Guild icon
swift-developers-japan
開発環境, ライブラリ / swiftui
Avatar
@_functionBuilder
8:05 PM
なるほど
8:06 PM
しかしアノテーションマジックが一気に増えましたね。
Avatar
Provide basic support for function builders, which allow the convenient creation of eDSLs for complex lists and hierarchies. An evolution pitch for this is forthcoming. That pitch proposes a somew...
Avatar
omochimetaru 6/3/2019 8:07 PM
43分前w
8:09 PM
ひえーなんだこれ
Avatar
omochimetaru 6/3/2019 8:10 PM
DSLみたいになってて
8:10 PM
サブビューを捕捉してるところは
8:10 PM
ただの型じゃなくてこれを使って
8:10 PM
解釈を動的にしょりしてるのか
8:10 PM
すごい機能きたな。
Avatar
Stackのinitの引数は @ViewBuilder content: () -> Content ってなってる
8:11 PM
これによってreturn書かなくてよくなってるのか (edited)
Avatar
omochimetaru 6/3/2019 8:12 PM
てかなんだこれ、ビューの子供も型に埋め込まれてる?
Avatar
焼きこまれてますね
Avatar
ViewBuilderでは今の所10個までっぽい static func buildBlock<C0, C1, C2, C3, C4, C5, C6, C7, C8, C9>(C0, C1, C2, C3, C4, C5, C6, C7, C8, C9) -> TupleView<(C0, C1, C2, C3, C4, C5, C6, C7, C8, C9)>
Avatar
TupleViewがあってそこに焼き込まれる
Avatar
omochimetaru 6/3/2019 8:13 PM
足りない時は包みを増やして回避かな
8:14 PM
if-elseとか、ifだけで条件によって表示されるViewもこうやって制御されてると
8:15 PM
Provide basic support for function builders, which allow the convenient creation of eDSLs for complex lists and hierarchies. An evolution pitch for this is forthcoming. That pitch proposes a somew...
Avatar
public protocol Identifiable 差分計算みっけ
8:17 PM
Listだと差分計算走りますね、HStackとかは多分なし。
Avatar
一方AndroidのDeclarative UIのComposeもコード生成で色々やってる http://intelligiblebabble.com/compose-from-first-principles/
Thousands of Developers from around the world attended Google I/O 2019 earlier this month. It was a particularly exciting I/O for me, as it…
Avatar
omochimetaru 6/3/2019 8:18 PM
それが多分Reactのkeyアトリビュートと同じことをやるやつかな
Avatar
そうっぽい
Avatar
こういうのもちゃんとある
Avatar
Sheetが気になる…
Avatar
セミモーダル的なやつですかね
Avatar
ですかねーApplePayとかAppStoreの決済時に出るSheetの大元みたいのっぽそう
Avatar
extension Optional : Publisher where Wrapped : Publisher { ええんかw
Avatar
SheetはmacOSだけだからセミモーダルとはまたちょっと違いそうでした
😫 1
Avatar
It's always been a goal of Swift to support declarative programming, and the language can be quite good for it, but some kinds of "declaration" fit better into the current language than others. In particular, heterogeneous trees with a lot of hard-coded structure — such as...
👀 4
Avatar
Kishikawa Katsumi 6/4/2019 12:24 AM
SwiftUIでビューコントローラはどうなるんだろう?
Avatar
仮想Viewが画面遷移も担っていて UIHostingControllerに渡して展開されるので、メジャーな機能は仮想Viewで使えて、そうで無いものはUIKit世界で処理、になると思います。SwiftUIの世界ではUIViewとUIViewControllerの区別はないように思える
Avatar
omochimetaru 6/4/2019 4:25 AM
これまでVCに書いてたような状態制御はViewのstateに埋め込むのか、それともその外側でEnvironmentの方にオブジェクトを置くのか??
Avatar
使い分けだと思う
Avatar
この VStack のtrailing closure で if 文かけるのなぜかわからないのですが説明できる人います? var body: some View { VStack(alignment: .center) { Text("1") if true { Text("2") } Text("3") } }
Avatar
omochimetaru 6/4/2019 4:40 AM
function builderのbuildIf
4:40 AM
そのクロージャ?の中身のコードは
4:41 AM
こいつのメソッド呼び出しにコンパイルされる
Avatar
まだよく分かってないけど、Environmentはグローバル・もしくは親子間での共有の状態、@Stateはローカルの状態、という感じかなと思ってます (edited)
Avatar
omochimetaru 6/4/2019 4:44 AM
buildBlockの呼び出しに変換されて、 TupleView<Text,Text?,Text>になるっぽい
4:45 AM
なるほど→envとstate
Avatar
その変換ってどうやって決定しているのかわからなくて。。。要素2つ目のテキストが Text? になるってコンパイル時にわからなくない。というかTupleの要素数決定できないなと思っていて。それが
こいつのメソッド呼び出しにコンパイルされる
って ViewBuilderがよしなにやってくれるって感じなんですかね
Avatar
omochimetaru 6/4/2019 4:46 AM
if文があると、buildIfになるんだと思います。
4:47 AM
IfだからoptionalになるのでText?
4:47 AM
なので、数は変動しない。
Avatar
omochimetaru 6/4/2019 4:47 AM
そう
4:49 AM
ifelseだとbuildEitherになって
4:49 AM
ConditionalContent<TrueContent, FalseContent>
Avatar
なんとなくわかりました。あざすあざす。これちなみに if をかいて buildif が使われる言語機能?って心当たりがあるプロポーザルとかあります? きっとSwiftUIだから特別構文を用意しているとかではないと思っているのですが
Avatar
発表の4時間前に出てたやつですね、DSL用にアノテーションが追加される
4:53 AM
585199874728001566
4:53 AM
ありゃ
Avatar
omochimetaru 6/4/2019 4:53 AM
Provide basic support for function builders, which allow the convenient creation of eDSLs for complex lists and hierarchies. An evolution pitch for this is forthcoming. That pitch proposes a somew...
Avatar
omochimetaru 6/4/2019 4:53 AM
プルリクがこれで、フォーラムが
4:54 AM
It's always been a goal of Swift to support declarative programming, and the language can be quite good for it, but some kinds of "declaration" fit better into the current language than others. In particular, heterogeneous trees with a lot of hard-coded structure — such as...
4:54 AM
Apple、裏で進めてた
Avatar
なるほど。これ事前に会話に出てたんですね。少しさかのぼったらワカッタ
Avatar
omochimetaru 6/4/2019 4:54 AM
いきなりさっき表舞台に出てきた言語機能
Avatar
proposalにどういう風にコードが変換されるかの例も書かれてます (edited)
4:54 AM
div { var v0_opt: [HTML]? if useChapterTitles { let v0: [HTML] = HTMLBuilder.buildExpression(h1(chapter + "1. Loomings.")) v0_opt = v0 } let v0_result = HTMLBuilder.buildOptional(v0_opt) let v1 = HTMLBuilder.buildExpression(p { "Call me Ishmael. Some years ago" }) let v2 = HTMLBuilder.buildExpression(p { "There is now your insular city" }) return HTMLBuilder.buildBlock(v0_result, v1, v2) }
Avatar
omochimetaru 6/4/2019 4:55 AM
HTMLだ
Avatar
いまいち上手くいかないと話題 #swift2 (edited)
4:56 AM
いまいち上手くいかないと話題 #swift-2
Avatar
omochimetaru 6/4/2019 4:56 AM
オープンに進めながら秘密の進捗もあるのAppleやばいw
Avatar
出したばかりのプロポーザルでβ版出しちゃうのはおいマジかwって感じではある
Avatar
omochimetaru 6/4/2019 4:57 AM
まあコアチームは決定権もってるからね
4:57 AM
evolプロセスは民主的だけど平等ではないw
Avatar
Property Wrapper(Property Delegate)もまだApprovedではないけど使われてるし (edited)
Avatar
omochimetaru 6/4/2019 4:58 AM
でもこれは受け入れられるだろうし、バランス感覚を感じる
Avatar
なるほどう。ありがとうございます!まだ理解半分ですが酔いが冷めたら改めてみます。それにしてもいきなり尖ったの入ってきた感があるな。。楽しくなってきた
Avatar
omochimetaru 6/4/2019 5:00 AM
情報量が多すぎるよねw
Avatar
本当にw
Avatar
omochimetaru 6/4/2019 5:12 AM
あーそうか
5:12 AM
だからこれって、ReactのJSXだ
5:12 AM
JSXはJSにそのままXMLを埋め込んで言語拡張したけど
5:13 AM
このアプローチは、ホスト言語のSwift自体にDSLサポートを追加することで
5:13 AM
ゲスト言語のビュー構造の記述をSwiftのままやってるんだわ
Avatar
そしてそれはGoogleが今年のI/Oで発表したJetpack Composeと同じ流れで、こちらもKotlinオンリー https://qiita.com/shyne/items/d149f4ccde308d7019af

はじめに

 Google I/O 2019で発表されたJetpack Composeについて調査しました。本記事ではその中でも特徴的な宣言的UIの構築を行うための文法についてまとめてみます。

注意点

本記事は5/2...
🤔 1
Avatar
yutailang0119 6/4/2019 5:58 AM
ここまでの話をikesyoからリモートで聴いてる
Avatar
omochimetaru 6/4/2019 6:15 AM
あれ?yutaroが現地でikesyoが京都?w
6:15 AM
ややこしい仕入れだ
Avatar
Jetpack Composeもアノテーションプロセッシングによるコード生成でDSLを実現してますね http://intelligiblebabble.com/compose-from-first-principles/ (edited)
Thousands of Developers from around the world attended Google I/O 2019 earlier this month. It was a particularly exciting I/O for me, as it…
Avatar
omochimetaru 6/4/2019 6:23 AM
このシンクロはなんなんだろう
6:24 AM
裏の談合があるのか、それとも、たまたま同時代に同種のブレークスルーが複数発生する現象のアレなのかな (edited)
Avatar
omochimetaru 6/4/2019 6:47 AM
考えてみたら、ORTのproposalで、
6:47 AM
GameObjectをtransformするなどのオペレーションの適用結果をGenericsで維持すると、
6:47 AM
どんどんややこしい型になるんだよ、ってストーリーが提示されてたけど
6:47 AM
あれは完全にSwiftUIの事だよなあ
6:48 AM
悟られないように2D View Systemの例は出さなかったに違いない
Avatar
伏線回収が鮮やかすぎる😎
Avatar
norio_nomura 6/5/2019 8:21 AM
Building a scrubbable UI timeline is really straightforward – I can't wait to see the tooling that evolves around SwiftUI. https://t.co/CDQUPa0yu5
Likes
190
👍 1
Avatar
Viewが値型だからこそ可能な世界観ですね
Avatar
omochimetaru 6/5/2019 8:37 AM
文字入力の過程もbindingされてるのか。日本語入力は大丈夫かな。
Avatar
norio_nomura 6/6/2019 12:31 AM
An experimental time traveling state store for SwiftUI - timdonnelly/SwiftUITimeTravel
Avatar
omochimetaru 6/6/2019 10:21 AM
AutoLayoutはなくなってるんかな、SafeAreaとの絡みを書く方法はあるんだろうか?
Avatar
ScrollView入れたらSafeAreaの中に入る予感がする
10:22 AM
SwiftUI、多分ちょっとでも難しいことしようとしたらUIKitにコンポーネント書かないといけないと思います
10:22 AM
あと個人的にはWebViewがどうしてもうまくいかないと思うのだけどどうなるんだろう、と。
Avatar
edgesIgnoringSafeArea IgnoringのためのAPIがあるということは、ScrollViewとか関係なく、全てViewは標準だとSafeAreaの中にレンダされるのかな
Avatar
omochimetaru 6/6/2019 10:33 AM
ふむふむ
10:34 AM
標準のNavigationBarを使わずに自作する時とか
Avatar
チュートリアルでは、 List でも普通にやったら SafeArea 考慮されてて、 edgesIgnoringSafeArea で SafeArea 無視されてましたね。
Avatar
omochimetaru 6/6/2019 10:34 AM
よく、SafeAreaのTopから49ptまでバーを伸ばす、とかやるけど
10:37 AM
ignoreできるだけだと厳しそうだな?
Avatar
複雑な AutoLayout を再現できるのかよくわかってない。今知ってる知識だけだとできなさそうだけど、 API 調べてみないと何とも。
10:50 AM
このあたりでかなりコントロールできる?
Avatar
SwiftUI のメソッドチェーンでインスタンス作り上げていく API 、あまり Swifty でない気がするけど宣言的に書くためには仕方ないのかなぁ。
Avatar
omochimetaru 6/6/2019 12:36 PM
値型だから毎回新しいインスタンスなのと、変換を型に焼きこんでるから、
12:36 PM
いわゆるselfを返すメソッドチェーンとは意味が違う気がしています
12:37 PM
というか、selfは返ってないから、 mapしてfilterして、、みたいな
12:37 PM
関数型データフローっぽい
Avatar
うん、そういう API があんまり標準ライブラリでは見られない気がして、 Swift っぽくない感じがしてる。
Avatar
omochimetaru 6/6/2019 12:39 PM
LazyMapぐらいですかね
12:40 PM
型がゴテゴテになっちゃうから、
12:40 PM
ORTが無い時はきつかったから
12:40 PM
言語が進化して採用可能になるパターンという理解
12:41 PM
必然的にこれまでのSwiftでは見られないやり方
Avatar
今まではそういう部分でも型消し使ってたのかな
12:42 PM
そうするとORTがパフォーマンスに与える影響はかなり大きそう
Avatar
omochimetaru 6/6/2019 12:43 PM
AnyPに逃してたはず
Avatar
https://developer.apple.com/documentation/swiftui/text/3276841-bold とか、↓的な考え方では値型のプロパティを不変にする意味はないから、新規インスタンスを生成するよりも当該プロパティを書き換えられるのが自然な気がするんだけど。型が変わってしまうものとの整合性を考えるとメソッドチェーンでインスタンス作ってくしかないのかな・・・。 https://qiita.com/omochimetaru/items/7265e440418b38088ccb

記事について

この記事は potatotips#39 という勉強会で発表した内容を再掲したものです。スライドの画像と、喋った言葉を載...
Avatar
omochimetaru 6/6/2019 12:46 PM
全く型が変わらないオペレータもあるのか。 これは従来だと bolded になるはずですね
12:48 PM
型が変わるやつとの整合性、っぽいですねえ
12:48 PM
functionBuilderとの相性もあるだろうけど、そこはニワトリタマゴだし。
12:49 PM
本当は、mutating funcでvarの型が変わるのが良かったのかも
12:49 PM
shadowingとして筋は通せる
Avatar
前にも話してたけど、イミュータブルオブジェクトを生成して状態変更していくパターンに対して値型のプロパティを変更するのは、型が変更できないデメリットがあるよね。
Avatar
omochimetaru 6/6/2019 12:50 PM
そういうことになりますね。
12:50 PM
inout考えると型変わるのはダメか。
12:50 PM
たしかに歪な気もしてきた
12:51 PM
SwiftUIをDSL風にすることが最大限に優先されてて、他はちょっと歪んでる
Avatar
shadowingとして筋は通せる
Rust 思い出した。↓とか。 fn main() { let x = 5; let x = x + 1; let x = x * 2; println!("The value of x is: {}", x); } https://doc.rust-lang.org/book/ch03-01-variables-and-mutability.html#shadowing
Avatar
omochimetaru 6/6/2019 12:55 PM
Rustはそれアリなのか〜
Avatar
mut let のときと let でシャドーイングのときを区別しないといけなくて、 Swift 的思考だとハマりそう。 (edited)
Avatar
omochimetaru 6/6/2019 12:56 PM
mut letの再代入のときはletの無いただの代入文だから見た目にすぐわかるのでは?
Avatar
ああ、見た目の区別というか、概念としてまったく異なるけどどっちも変更しているように見えてしまうというか。
Avatar
omochimetaru 6/6/2019 12:58 PM
Swiftでも、引数とローカル変数とか
12:58 PM
letでshadowできる場面もあるから
12:58 PM
それの仲間に見えるんじゃないか
Avatar
うーん、そうかも?でも↑のコード見たときはびっくりしたなぁ。
Avatar
↓とかできるの面白いな。これ(二重の padding )はインスタンス生成してないとできない。 Text(string) .padding(EdgeInsets(top: 8, leading: 16, bottom: 8, trailing: 16)) .background(Color.white) .cornerRadius(16) .padding(EdgeInsets(top: 10, leading: 10, bottom: 10, trailing: 10))
Avatar
padding重ね、これ面白かったですね https://twitter.com/twostraws/status/1136130799600668672
SwiftUI, folks. #WWDC19 #Pride
Retweets
154
Likes
802
👍 2
Avatar
これわかりやすいですね🙂 (edited)
1:42 PM
ORT の恩恵もわかりやすいですね。 https://twitter.com/Pahitos/status/1136468991051018240
@twostraws And this is the type of that view… What’s the benefit of relying so heavily on the type system to represent those views? cc @jckarter
Avatar
@omochimetaru 昨日話してたカスタム NavBar 、とりあえず↓みたいに GeometryReader 使ってできそう。もっといいやり方ありそうだけど。 struct ContentView : View { var body: some View { VStack { GeometryReader { geometry in VStack { Spacer() .frame( width: geometry.size.width + geometry.safeAreaInsets.leading + geometry.safeAreaInsets.trailing, height: geometry.safeAreaInsets.top ) .background(Color.blue) ZStack { Spacer() .frame( width: geometry.size.width + geometry.safeAreaInsets.leading + geometry.safeAreaInsets.trailing, height: 100 ) .background(Color.red) Text("Custom Bar") } } .edgesIgnoringSafeArea(.init([ .top, .leading, .trailing ])) } } } } (edited)
Avatar
omochimetaru 6/7/2019 1:05 AM
おお、そうやって自分で計算書けるんですね それなら大抵のケースは最悪どうにでもできるな
Avatar
layoutPriority とかもあるみたいだから、かなりのことはできそう。 https://developer.apple.com/documentation/swiftui/view/3278584-layoutpriority
Avatar
omochimetaru 6/7/2019 1:09 AM
ふむふむ
Avatar
どこまでできてどこからできないのかわからないから、 NDA 解けたら、 Auto Layout のこれを SwiftUI でできますか?コンテストとかやったらおもしろいかもね。
1:13 AM
階層を超えた centerX 合わせとかきつそう。
Avatar
GeometryReader 胸熱
1:53 PM
そして struct ContentView : View { @State var int = 0 var body: some View { let red = int > 0 Text("Hello World") .background(red ? Color.red : Color.blue) } } ↑はビルドエラーになるけど struct ContentView : View { @State var int = 0 var red: Bool { int > 0 } var body: some View { Text("Hello World") .background(red ? Color.red : Color.blue) } } ならOKですね
Avatar
SwiftUI作ってる人のアカウントをみつけた https://twitter.com/ricketson_
SwiftUI @ 
Tweets
2725
Followers
607
Avatar
Kishikawa Katsumi 6/7/2019 5:12 PM
5:13 PM
SwiftUIにMultiline Literalを与えるとプレビューが混乱する。 うまくいくときもある。 これはなんか難しいことをしてそう。
5:14 PM
消して再入力してやり直すとうまくいったりする
Avatar
↑と同じネタで、Listの中のTextの高さが上手くいかない、という報告もあがってましたね。
Avatar
Kishikawa Katsumi 6/7/2019 5:40 PM
バグレポートはいっぱいきてて大変そう。どんどん送らないと。
5:43 PM
Image("owl") .resizable() .edgesIgnoringSafeArea([.top, .horizontal]) ^ これを Image("owl") .edgesIgnoringSafeArea([.top, .horizontal]) .resizable() ^ このように入れ替えられない、とかけっこう難しいと思うんですよね。
Avatar
これは「かなり厳しいGenericsだ」みたいにKnownIssueとしてつけられてたので、何かしら治るんじゃないかなと予感しています (edited)
Avatar
Kishikawa Katsumi 6/7/2019 5:45 PM
そうなんですね。直ってほしい。
Avatar
Kishikawa Katsumi 6/7/2019 6:54 PM
インスペクタが大変なことになった。
Avatar
こういうセンターラインのずれとか、どうやって補正すればいいんだろう
10:17 PM
あとこのスターはボタンなのだけど、destinationへの遷移が優先されてしまう
Avatar
WWDCでSwiftUIが発表されてから数日が経ちました。一気に世界が変わった気がしますね。 ただ、UIKitと同様にSwiftUIはオープンソースでは無いため、我々開発者は依然挙動をエスパーしながら開発する必要があ...
👍 1
Avatar
今更ながら @State なpropertyが中身のオブジェクトのアクセサ持ってるの、これ使ってるんですね https://twitter.com/v_pradeilles/status/1136755695427018752?s=21
dynamicMemberLookup now supports typed KeyPaths 🤯 This means a type can now seamlessly integrate the API of its dependency within its own API ⬇️
Retweets
105
Likes
369
3:05 PM
色々悪いこと出来そう
Avatar
Kishikawa Katsumi 6/10/2019 9:19 PM
https://twitter.com/terhechte/status/1138106114304819201 セル内のボタンがタップになるのは他の人も困ってるみたい
Did anybody figure out how to create a List row in #SwiftUI that forwards the tap events to controls in the cell? (i.e. List { VStack { Text(..) Button(action: ..) { Text("Press me") } } }` Trying to tap the button just selects the cell
Avatar
dynamicがPure Swiftのメソッド・プロパティにも付けられるようになってて、_@dynamicReplacememtで差し替えられる機構がホットリロードに使われてるようで面白い! https://forums.swift.org/t/how-does-the-hot-reloading-work-in-xcode11/25312 https://forums.swift.org/t/dynamic-method-replacement/16619
I'm interested in the hot-reloading demonstrated for SwiftUI. How is this implemented? Could something similar be achieved for a desktop application using the swift toolchain independently of XCode?
Pitch: Dynamic method replacement Introduction Objective-C allows swapping method implementations "swizzling" e.g. via the method_setImplemenation API. We can make use of this facility in Swift by marking methods with @objc dynamic. class Thing : NSObject { @objc dynamic ...
11:51 PM
The Swift Programming Language. Contribute to apple/swift development by creating an account on GitHub.
11:53 PM
5.0 runtimeへのバックデプロイもやろうとしてる https://github.com/apple/swift/pull/25340
This will allow backward deployment to a swift 5.0 runtime. rdar://problem/51601233
Avatar
おお
Avatar
おお
Avatar
https://github.com/apple/swift/pull/20333 ここが実装スタート地点か
Implement dynamic function replacement as described in https://forums.swift.org/t/dynamic-method-replacement. Allow dynamic on non-@objc classes, struct, and enum functions, properties, initializer...
Avatar
置き換えられるのは、あらかじめdynamicと宣言したモノだけか。
Avatar
あんま興味なかったんだけどhot reloadに使ってると聞いて興味が出てきた
12:51 AM
単純にクロージャ型の変数にしてるんだろうか?
Avatar
ORTが実装されるときにORTのdynamic replacementもガシガシ実装されてたけど
12:59 AM
型が同じじゃないといけない?けどORTなら消えてるからView.viewの置き換えができるんかな
Avatar
SwiftUIでリアルタイムプレビューする方法がわからないのですが、どなたか教えていただけないでしょうか?
Avatar
Appleのサンプルを見ると良いですよ。 なおOSもβ版入れないと、表示されないので注意が必要です。
Avatar
ああ、、そうでしたか....
8:19 AM
了解です
8:19 AM
ありがとうございます
Avatar
無事使えるようになりました!!
12:23 AM
ありがとうございます
Avatar
norio_nomura 7/1/2019 2:32 AM
A small toy implementation of #SwiftUI for the Web: https://t.co/UjxV4AodzQ
Likes
167
Avatar
CGDrawingViewがLabelかButtonかなどのメタ情報の取り方を知ってる方いますか?色々試したんですが無さそうでして
Avatar
omochimetaru 7/24/2019 9:18 AM
ネイティブ側のビュー(CGDrawingViewもその一つ)にSwiftUIのビュー構造は保持されないので、取れないと思います
Avatar
ですよねー、SwiftSyntaxでコードを喰わせればなんだったを保持して置くことは出来そうな気もしてたんですが
Avatar
yutailang0119 8/20/2019 3:58 AM
Xcode 11 beta 6で Binding<Value>.value -> wrappedValue に変更されているみたいだけど、Docに反映されていなそう? https://developer.apple.com/documentation/swiftui/binding?changes=latest_beta (edited)
4:00 AM
Doc diffにも出てこないし、こんなことあるんだ
Avatar
norio_nomura 8/20/2019 7:20 AM
beta 5とbeta 6でSwiftUI.framework/Versions/A/Modules/SwiftUI.swiftmodule/x86_64.swiftinterfaceを比較しても、その辺り変わっていない様な。
Avatar
yutailang0119 8/20/2019 8:47 AM
おや...なんでbeta 6にしたらエラーになっているのだろう...
Avatar
SwiftUIってiOS13以降じゃないとダメなんですね... 既存のプロジェクトにも適用できるのかなと思ってました
Avatar
「Static Framework が含まれる環境で SwiftUI のプレビューを動作させるには Linker flags に -fprofile-instr-generate を追加する必要がある」この原理を考察してみました(多分間違ってる)。 考察に納得できてないので、もし知ってることがあれば教えていただきたいです!(この窓で良いのかな…) https://qiita.com/AkkeyLab/items/a698545f2423b9b1dec9

はじめに

この記事は CyberAgent Developers Advent Calendar 2019 9日目の記事になります。 今回は、コンパイラ...
Avatar
記事は見たんですけど、特に違和感無かったので、問題ないと思いますが、 どのへんが疑問として残っていますか?
3:49 AM
(想像は付いているんですが、すれ違いがあると余計に混乱させてしまいそうなので)
Avatar
「フラグを追加すると Static Framework を参照可能になるのはなぜなのか」というところがまだ理解できていない感じになります。
Avatar
Kishikawa Katsumi 12/18/2019 4:13 AM
確認なんですけど、 問題を解決する方法は2つで、 a) アプリのプロジェクト側でCode CoverageのオプションをOFFにする(かつ-fprofile-instr-generateなし) b) Code CoverageのオプションはONかつLinker Flagに-fprofile-instr-generateを渡す のどちらかなんですよね? (edited)
Avatar
はい、その2つの挙動を確認しています。
Avatar
フラグを追加すると Static Framework を参照可能になるのはなぜなのか
カバレッジ計測を有効にしてコンパイルすると、通常のコードに加えて、 カバレッジランタイムライブラリの呼び出しが追加されたコードが生成されます。 カバレッジランタイムライブラリは、実行ファイルをビルドするときに、事前に用意されたものを、リンカーが結合することで有効化されます。 Static Frameworkの中にカバレッジランタイムライブラリの呼び出しが含まれているので、オプションを与えていないと、リンカーがライブラリを提供しないので、呼び出そうとしているライブラリ関数が見つからず、undefined symbolエラーが出てしまいます。
(edited)
👀 1
Avatar
Kishikawa Katsumi 12/18/2019 4:21 AM
Static Frameworkの中にカバレッジランタイムライブラリの呼び出しが含まれているので
気になってるんだけど、Code Coverage ONでビルドされて配布されてるのかな?
Avatar
As for needing -fprofile-instr-generate, IIRC we disabled code coverage because of App Store submission issue. Though apparently Carthage 0.26 has formally addressed the issue. Perhaps we can now reenable it.
4:29 AM
I&#39;ve been scratching my head all day as to why our new Carthage static libs setup works great for all our dependencies except Reactive(ObjC|Swift|Cocoa). For these frameworks, when I try to...
4:29 AM
記事から参照されている、ReactiveSwiftの事例に関しては、有効にして配布していたけどやっぱやめた、って話のようですね。
4:34 AM
$ lipo -extract x86_64 FirebaseCore -output FirebaseCoreX64 $ nm -a FirebaseCoreX64 | grep -i llvm ダウンロードしたFirebaseCoreには含まれてなさそう。
Avatar
Kishikawa Katsumi 12/18/2019 4:35 AM
そうなんだよねえ。私も環境作って試してるんですけど。
Avatar
cocoa podだと設定されるんじゃないですか?(調査中
Avatar
参考になるかわかりませんが FirebaseCore が見当たらないと言われたアプリでは Firebase/Analytics を pods で入れてます。
Avatar
Kishikawa Katsumi 12/18/2019 4:39 AM
なるほど。ちなみに厳密にはFirebaseCoreが見つからないんじゃなくて、FirbaseCoreが呼び出そうとしているカバレッジ計測のランタイムライブラリが見つからないというエラーです。 なのでリンカフラグを付けてカバレッジ計測のランタイムライブラリがリンクされるようにするとエラーが消える。
👀 1
4:40 AM
Static Frameworkということだから、ビルド済みのものを考えていたけど、
4:40 AM
CoocaPodsによってビルドされるStatic Frameworkか。
Avatar
そうですね、referenced from: ___llvm_profile_runtime_user in FirebaseCore なので、英文的には、見つからなかったのはFirebaseCoreじゃなくて、 FirebaseCoreの中から 参照している llvm_profile_runtime_user が、見つからなかった、ですね。
👀 1
Avatar
Kishikawa Katsumi 12/18/2019 4:41 AM
で、プロジェクトでカバレッジを有効にしているとPodsの方のビルドもカバレッジが有効になって、コードカバレッジが有効なStatic Frameworkが生成されている。
4:43 AM
で、SwiftUIのプレビューに使われるバイナリはスキーマの設定(コードカバレッジの設定)のいかんにかかわらず、カバレッジなしのバイナリになってるから、リンクできない、かな? (edited)
Avatar
あー、そうっすね、「カバレッジを有効にする」っていう部分の操作が、
4:44 AM
CocoaPods流のxcworkspaceでの環境においてどういうオペレーションなのかにもよるのか。
Avatar
Kishikawa Katsumi 12/18/2019 4:44 AM
^ で書いたのはたぶん合ってる気がする。
Avatar
podsでAnalyticsを入れるところまで追いついたけどこの先がわからない。。
Avatar
Kishikawa Katsumi 12/18/2019 4:46 AM
ちなみに、-fprofile-instr-generateで解決するのはそのまま出荷されてしまうとあまり嬉しくないのでカバレッジをOFFにする方がいいと思いますよ。 (スキーマでON/OFFするやつはこの問題がなかったとしても使わない方が良くて、必要な時にコマンドラインから渡す、方がいいです)
Avatar
実際にカバレッジを無効にしてみると tapple でも AkkeyTV でも Xcode Preview が正常に動作するようになりました!しかし、テストを書きながら開発を進めているため、この解決方法はボツにしました。
僕もここがよくわからなくて、
4:48 AM
テストするときはカバレッジ有効になってて、
4:48 AM
普段のデバッグ実行はカバレッジ無効になってる、 って状態を目指した方が良いと思う。
4:48 AM
普段LeaksとかTime Profileするときはprofileビルドはまた別でビルドキャッシュが効いてる感じだしできるはずだと思うんだけど
4:49 AM
CocoaPodsでできるのかどうかとかは知らないです
Avatar
Kishikawa Katsumi 12/18/2019 4:49 AM
Carthageでも問題になったけどあのスキーマにある設定を使うと基本的にビルドするのが全部Instruments付きになってしまう。アーカイブの時だけはならないから大体問題ないようになってるけど。。。 (edited)
Avatar
あのスキーマの設定ってなんですか? Run, Test, Profileで別々に設定があって、 Testの Gather coverateのチェックボックスの事?
Avatar
Kishikawa Katsumi 12/18/2019 4:51 AM
そうそれ > Gather coverate
Avatar
edit scheme > test > code coverage ここのチェックを外すことを「カバレッジを無効にする」と表現してました。
Avatar
うお、ほんとうだ
4:55 AM
そうかこれ、Product CacheはRun/Test/Profileではわかれてなくて、
4:55 AM
Debug/Releaseでしか区別が無い・・・?
4:55 AM
で、たしかに、Testスキームでカバレッジを有効にすると、Runスキームのビルドも有効になるんですね。
Avatar
ちなみに、-fprofile-instr-generateで解決するのはそのまま出荷されてしまうとあまり嬉しくないのでカバレッジをOFFにする方がいいと思いますよ。
出荷というのは、ストアにアップロードするという認識で合ってますか?ここらへん調べてるときに、カバレッジが有効な状態のアプリをアップロードしようとするとエラーで弾かれるという投稿を見かけたので問題ないだろうと判断してました、、
Avatar
Kishikawa Katsumi 12/18/2019 4:58 AM
まあだいたい問題ないんですけど、
Avatar
へえ、AppStore親切だ。 > カバレッジが有効な状態のアプリをアップロードしようとするとエラーで弾かれる
4:58 AM
この構成ならアーカイブビルドのときもPods/Firebaseが専用にビルドされるから、大丈夫そうですね。
4:59 AM
Carthageで事前ビルドしたフレームワークとかだと怪しそう・・・
Avatar
Kishikawa Katsumi 12/18/2019 4:59 AM
出荷されてしまうとっていうのは正確じゃなかった。リリースビルドがカバレッジ有効になっちゃう恐れがあって、例えば申請でエラーになるとか今回みたいなリンクの問題とかが起こるんで、
5:00 AM
使わない方がいい、ですね。 (edited)
5:01 AM
ほとんどの人は毎回手元でユニットテストのカバレッジとらないし、CIだけで有効になってる方がいいはず。
Avatar
そうすると理想の解決策は、
5:02 AM
SwiftUIの内部ビルドに
5:02 AM
カバレッジ有効フラグを適切に注入する事ですけど
5:02 AM
多分そんな設定できる場所無いよな。
Avatar
Kishikawa Katsumi 12/18/2019 5:03 AM
リンクエラーまでは再現した。
5:03 AM
@omochimetaru プロジェクト共有しましょうか?
Avatar
お、ほしいです
Avatar
ほとんどの人は毎回手元でユニットテストのカバレッジとらないし、CIだけで有効になってる方がいいはず。
納得です! Xcode でデフォルト OFF にしてほしいなぁと思っちゃいました
5:07 AM
Code CoverageのチェックはデフォルトOFFじゃなかったかな?
Avatar
あ、かもしれないですね、、
Avatar
Kishikawa Katsumi 12/18/2019 5:08 AM
@omochimetaru DerivedData 消した後でも再現したから再現すると思う。 カバレッジ有効になってるのでContentViewのプレビューをしようとしたらリンカエラーが見えると思います。カバレッジOFFでプレビューできる。
5:09 AM
CocoaPodsで最低限必要なFirebaseのインストールをして、念のためFirebaseのimportと呼び出しのコードを書いて、コードカバレッジをONにする、という手順で作りました。
Avatar
お、再現できました。
5:14 AM
Previewしようとするとビルドログができて、
Avatar
Kishikawa Katsumi 12/18/2019 5:14 AM
まあこれはわかりやすいのでバグレポートでいいんじゃないかな? 期待する挙動としては a) 問題なくプレビューできる b) エラーメッセージをわかりやすくする のどちらかじゃない?
👀 1
Avatar
そっちはアプリ自体はビルドできているんですね。
Avatar
Kishikawa Katsumi 12/18/2019 5:14 AM
アプリもビルドできないって話でしたっけ
Avatar
あいや、内部的な話で
5:15 AM
SwiftUI Previewが内部でアプリをビルドした上で、
5:15 AM
さらにContentView.swiftのプレビュー用になにかしようとしていて、
5:15 AM
その最後のフェーズのリンクで落ちてるっぽいなあということが見えた。 (edited)
Avatar
プレビューが表示されてる裏(見えないけど)でシミュレータみたいな感じで立ち上がるやつですよね?
5:18 AM
動画を起動後自動再生するアプリでプレビューさせようとすると音だけ聞こえてくるので気が付きました。
Avatar
そうです、音なるのかw
5:19 AM
裏でContentView.swiftをソース書き換えしたりしてます。
Avatar
そこらへんがどんな仕組みなのか気になってこの記事読んでみたりもしました。 https://tech.guardsquare.com/posts/behind-swift-ui-previews/
Recently, Apple introduced ‘SwiftUI’, a new framework for building native UIs across all Apple’s platforms. The core selling point of the framework is that it allows defining application interfaces in a declarative way. SwiftUI is expected to significantly simplify the...
Avatar
あ、そうそう、これ。 > TextView.3.preview-thunk.swift
5:26 AM
状況としては、SwiftUI Preview用の内部ビルドが、XcodeのCode coverageの設定にうまく追従できてないっていう不具合というか機能不足だと思います。
👀 1
Avatar
Kishikawa Katsumi 12/18/2019 5:28 AM
リンカーフラグは見るけどスキーマにあるCode coverageの設定は無視している、っていうのがおかしい、ということでそうですね。 私はスキーマのCode coverageのチェックボックスを直した方がいいと思うけど。。。😛
Avatar
SwiftUI Preview の内部ビルドに対しても xcspec みたいな存在があるのか気になりますね。あるとするなら、そこに Code coverage の設定を見る処理が不足しているってことになりそうですね。
5:37 AM
すごく勉強になりました! ありがとうございます!!
Avatar
全く手元で試してないので妄言かもしれないですが、Preview用のターゲットを作ると解決できないですかね?
Avatar
あ〜そっか。
5:42 AM
Code Coverageの設定が別にあればいいもんね。
Avatar
Preview用のターゲットではカバレッジをオフにして、本体はオンにする
5:44 AM
というかこの場合はbuild configurationを弄る必要はなくて、カバレッジ設定をターゲットを切り替えるだけでいいからターゲットを作るまでもなくpreview用のスキーマを作れば事足りそう
Avatar
同じターゲットのスキーマ違いか!
5:46 AM
需要初めて見た
5:46 AM
あれ、それプロダクトキャッシュちゃんと分離されるのか?
Avatar
Kishikawa Katsumi 1/6/2020 12:11 AM
@swiftbot import SwiftUI struct ContentView: View { var message: String @State var isOn = true var body: some View { VStack { VStack { Toggle(isOn: $isOn) { Text("Switch") .font(.title) .foregroundColor(Color.white) } } .padding() .background(isOn ? Color.purple : Color.orange) Text(message) .font(.largeTitle) .foregroundColor(.blue) } } } struct ContentView_Preview: PreviewProvider { static var previews: some View { ContentView(message: "Hello SwiftUI Playground!") } }
Avatar
Kishikawa Katsumi 1/6/2020 12:13 AM
MacStadiumのマシンで作ってみた。
Avatar
omochimetaru 1/6/2020 12:14 AM
うおおおおおお
Avatar
Kishikawa Katsumi 1/6/2020 12:14 AM
PreviewのModifierとかこれからサポートする予定。今はまだ固定サイズでしか出ない。
12:14 AM
よかったら適度にテストしてほしい 🙏
Avatar
omochimetaru 1/6/2020 12:15 AM
コマンドライン出力すっとばして先にSwiftUIなんですね
Avatar
Kishikawa Katsumi 1/6/2020 12:15 AM
脆弱性とか見つけたら速やかにDMで教えてください。
12:15 AM
まあ、macOSの環境で動くやつ作ってもよかったけど、それはすぐできるし、あんまりやる気にならなかった。
Avatar
omochimetaru 1/6/2020 12:16 AM
やる気なるほど
Avatar
Kishikawa Katsumi 1/6/2020 12:18 AM
あとはOSをスイッチするためのフラグがいい感じのが思いついてなくて。
Avatar
omochimetaru 1/6/2020 12:22 AM
あ、同じbotなのか。
12:22 AM
import SwiftUIで切り替えてるんですか?
Avatar
Kishikawa Katsumi 1/6/2020 12:23 AM
大正解!さすがですね 😄
🍎 1
12:24 AM
同じじゃなくてもいいんですけど、同じ方が簡単だったから。
Avatar
Kishikawa Katsumi 1/6/2020 12:53 AM
@swiftbot import SwiftUI struct ContentView: View { var body: some View { VStack { // 1. Rectangle() .stroke(Color.blue, lineWidth: 10) .frame(width: 100, height: 100) // 2. Circle() .fill(Color.red) .frame(width: 100, height: 100) // 3. Capsule() .fill(Color.green) .overlay( Capsule() .stroke(Color.black, lineWidth: 10) ) .frame(width: 200, height: 100) // 4. RoundedRectangle(cornerRadius: 20) .fill(Color.yellow) .frame(width: 100, height: 100) } } } struct ContentView_Preview: PreviewProvider { static var previews: some View { ContentView() } }
Avatar
すげえ!
Avatar
Kishikawa Katsumi 1/6/2020 12:57 AM
すごいでしょ 😄
12:58 AM
今夜か明日にはコード公開するので納得いってないところアドバイスしてほしい。
👀 3
Avatar
すごい
Avatar
Kishikawa Katsumi 1/6/2020 2:00 PM
ありがとう 😄
swift 2
Avatar
すごい
Avatar
Kishikawa Katsumi 1/6/2020 3:10 PM
どうも。Webの方もなんとなく動くようになりました。 https://swiftui-playground.kishikawakatsumi.com/ 実力不足によりWebを書き始めるととたんに開発スピードが落ちる。。。
🐌 1
Avatar
中華フォント問題は深刻。技術的な文化のinvasionだと思ってる。
Avatar
Kishikawa Katsumi 1/6/2020 4:24 PM
うーむ。
Avatar
en_US (Default)だとほとんどのシステムで zh_Hans が ja_JP より高いので...
4:28 PM
(UnicodeがCJK包摂漢字なんてことしたのが全ての原因)
Avatar
Kishikawa Katsumi 1/6/2020 4:29 PM
CJK包摂漢字が元凶なのはそうですね。包摂していいものとダメなものが理解されてなかった。
4:36 PM
struct ContentView_Preview: PreviewProvider { static var previews: some View { ContentView() .environment(\.locale, .init(identifier: "ja")) } } とすると手元ではOKだけどサーバーでは変わらない。 日本語にフォールバックされる要件が相変わらずよくわからない。
Avatar
Kishikawa Katsumi 1/6/2020 4:50 PM
あ、いや .environment(\.locale, .init(identifier: "ja")) 関係なかった。
4:55 PM
単純にシミュレータのPreffered Language Orderに日本語があるかどうかか。
Avatar
だと思いますが
5:02 PM
アプリでランタイムで上書きできるはず。
Avatar
Kishikawa Katsumi 1/6/2020 9:35 PM
https://github.com/kishikawakatsumi/swiftui-playground ^ Playgroundのコード公開しました。
SwiftUI Online Playground. Contribute to kishikawakatsumi/swiftui-playground development by creating an account on GitHub.
9:38 PM
Previewを出力するためにまず、 https://github.com/kishikawakatsumi/swiftui-playground/blob/master/PreviewProvider/Sources/PreviewProviderParser/main.swift ^ SwiftSyntaxで入力されたコードからPreviewProviderに準拠しているものを探します。 これは自由入力されたコードから探さないといけないので、方法はいくつかあるけど必要なプロセスだと思う。
9:42 PM
で、PreviewProviderはGroupとか複数画面サイズとかできるのでそれに対応するために(ちゃんと対応できてないけど。。。)一回ビルド、実行してPreviewProvider.previewsをMirrorを使ってリフレクションで構造を読み取ります。 modifierの指定を集めるのと、Groupを探してGroupがあったらXcodeの挙動と一致するように分割します。
9:45 PM
で、そのあとはUIHostingControllerに入れて画像として出力するのですが、その際に some View の方がわからないと渡せないので上記のリフレクションで構造を読む際に実際の型名を書き込んだソースコードを生成して、再度ビルドして実行します。 2回目の実行で画像が出力されます。
9:49 PM
改善したいところは2つあって、1回目のリフレクションで構造を読むところ、そもそもあまり正しく動いてないし、コードもいきあたりばったりで書いたやつだから美しくしたい。
9:52 PM
もう一つは、そもそも2回ビルドして実行しているのを可能ならやめたい。。。 実際の型を書き込むために1回目でリフレクションとコード生成、2回目で実行、としているのでこれはもっとスマートな方法があったら解決する気がします。 妥協したくないポイントは、PreviewをGroupやサイズなどで複数出力できるというところです。
9:52 PM
それを守りつつ、より良い方法を探したい。
Avatar
試してないんで分からないですがAnyView使って型消去じゃダメですか?
Avatar
Kishikawa Katsumi 1/7/2020 1:12 AM
Mirrorから得たものはAnyになっちゃうのでAnyViewにもできないのです😢
Avatar
なるほど
Avatar
ちょうど #swiftbot-sandbox で話題になってるAnyEquatableの手法を応用すると、もしかしたらですがMirror無しに動かせるかもしれないです
1:26 AM
GroupとModifedContentはstructなので、これに後付けでAnyViewを吐き出すextensionを生やしてあげればいいのかな
Avatar
Kishikawa Katsumi 1/7/2020 1:43 AM
あ、言われてなんかひらめいた気がします。ちょっとやってみます。
Avatar
Kishikawa Katsumi 1/7/2020 2:21 AM
意外とMac miniのスペックでもがんばってる気がする。 だんだん重くなっていくような挙動をしてたけど、原因は画像アップロード先のRate Limitで画像を表示できなくなってるだけだった。とりあえず直接配信するようにして様子を見る。
Avatar
キャッシュしてます?
3:07 AM
多分Runボタンいきなり押す人おおいとおもうので
3:07 AM
同じリクエスならキャッシュから読むとかしないとしんどい
Avatar
Kishikawa Katsumi 1/7/2020 3:08 AM
実は何もしてないです。
3:08 AM
今夜あたりその辺に手をつけてみます。
3:11 AM
ただ、実行はしないといけないんで、シンプルにならなさそうで困っています。 例えば Text("\(Date())") みたいなコードがあったら文字列としては一致してるけど結果は変わっちゃうとか。
Avatar
真面目にやるならあれですが、この最初にはいってるデフォルトのコードの場合だけでもcacheすれば相当 (edited)
3:40 AM
トラフィック減りそう
3:40 AM
logとかみてみないと分かりませんが。
Avatar
Kishikawa Katsumi 1/7/2020 3:46 AM
実行されてるのはほとんどデフォルトのコードなんで、それは大胆にキャッシュして良さそうです。
Avatar
@swiftbot import SwiftUI extension View { func when<NewView: View>(_ cond: Bool, @ViewBuilder then apply: (Self) -> NewView) -> some View { Group { if cond { apply(self) } else { self } } } } struct MyView: View { var body: some View { Text("a") // .when(true) { // $0 // } // .when(Bool.random()) { // $0.padding(10).background(Color.yellow) // } // .when(Bool.random()) { // $0.padding(10).background(Color.green) // } .when(true) { view in VStack { Text("begin") view Text("end") } } } } struct MyView_Preview: PreviewProvider { static var previews: some View { MyView() } }
Avatar
swiftbot BOT 1/22/2020 7:28 AM
Avatar
@swiftbot import SwiftUI extension View { func when<NewView: View>(_ cond: Bool, @ViewBuilder then apply: (Self) -> NewView) -> some View { Group { if cond { apply(self) } else { self } } } } struct MyView: View { var body: some View { Text("a") .when(true) { $0 } .when(true) { view in VStack { Text("begin") view Text("end") } } } } struct MyView_Preview: PreviewProvider { static var previews: some View { MyView() } }
Avatar
swiftbot BOT 1/22/2020 7:28 AM
22:17: error: cannot convert value of type 'VStack<Content>' to closure result type '_' VStack { ^~~~~~~~
Avatar
多分ここでいいんだと思うんだけど
1:04 PM
Minimum reproducible code. It may be fixed on 11.4 but similar issue is in SwiftUI, which are not fixed on at least 11.4b2. https://t.co/aV1bRx0CvM
1:04 PM
この例は Combine だけど、これ地味にしんどい
1:05 PM
SwiftUIは11.4b2でもweakにならない
1:05 PM
明示的にframeworkをweakでリンクしないとダメ
1:06 PM
なんか素敵なworkaroundないですかね。これspmのライブラリだと、ライブラリ利用者側に責任がまわってしまうので
1:06 PM
より顕著
Avatar
Kishikawa Katsumi 2/26/2020 7:13 PM
問題を間違って捉えてるかもしれないですが、 CombineやSwiftUIを使ってるライブラリで、でもそれは内部でうまいことハンドリングしていて下のバージョンでも使えるようになっている、ということですよね。 でその際、下のバージョンで利用したい場合は、利用者側がWeak Linkの設定をする、というのはそういうもので、合理的に見えます。
Avatar
えっと。
7:34 PM
そういうものじゃないはずなのにバグでそうなってしまってるってことですかね。
7:37 PM
SwiftUIやCombineを使うライブラリで、そのmin target platformが例えばiOS 11とかになってる場合、そのライブラリの実装では@available()や#available()でAPIを制限すれば、本来なら、そのライブラリを使う側は-weak_frameworkなしに@importするだけで(dyldのframwork linkの力で)自動でシンボルがweakになってランタイムで問題なく実行できるんです
7:38 PM
しかしどうやらcombineやswiftuiにはいくつか問題のあるシンボルがまだ含まれているようで、それらがweakリンクにならないんです
7:38 PM
nm -mgでバイナリみるとわかると思います
7:39 PM
そういう漏れ出たシンボルはそのライブラリを利用してる側からは知ったこっちゃないことなのに-weak_frameworkしないと実行時にdyldがcrashするということになってしまって
7:39 PM
あらウザイ
7:39 PM
ってことです
7:39 PM
Tweetのスレッドでも参照していますが、https://forums.swift.org/t/weak-linking-of-frameworks-with-greater-deployment-targets/26017/11 Just WorksTMらしいんですよね本来は (edited)
That should definitely work as long as the framework is indeed weak-linked. If it's not, then either the SwiftUI folks have missed some availability (more likely), or there's something in Xcode (or in your project specifically?) that's causing it to be strong-linked. It's supp...
7:40 PM
そうなってないんだけど。
Avatar
Kishikawa Katsumi 2/26/2020 8:09 PM
なるほど、Availabilityでちゃんと下のバージョンで使わないようにコントロールしたらdyldはweak linkとして扱ってくれる(はず)なんですね。
Avatar
ですです
9:34 PM
そうなってないんだけど
Avatar
Xcode 11.4 beta 2で直ってるような。
11:19 PM
Combine.swiftmodule/x86_64.swiftinterface のdiffを見ると、いくつかのextensionに@available(OSX 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *)が追加されてて、 +@available(OSX 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *) extension Subject where Self.Output == Swift.Void { public func send() } ってのも含まれてる。 (edited)
Avatar
combineはなおってます
Avatar
生成されるバイナリのnm -mgでも (undefined) weak external (extension in Combine):Combine.Subject< where A.Output == ()>.send() -> () (from Combine) になる。
Avatar
SwiftUIは治ってない
11:23 PM
直ってない
Avatar
なるほど。
Avatar
SwiftUIのswiftinterface内で@availableが付いていないのを全部チェックしないといけないのですね。
11:31 PM
型の@availableよりextensionの@availableが緩かったらコンパイラでエラーにして欲しいな。
Avatar
extensionが同じバイナリ/moduleのなかにあるかどうかわからないんじゃないですかね
Avatar
Improvementなissueあった。 > コンパイラでエラーにして欲しい https://bugs.swift.org/browse/SR-10897 (edited)
Avatar
とりあえずコメントしてvoteしておいた。 (edited)
Avatar
おー。
Avatar
今年のGWより独学でSwiftUIを勉強し始めた者です。みなさんの視点からみて、SwiftUIの将来性はどう思われますか?
Avatar
Kishikawa Katsumi 5/25/2020 7:53 AM
将来的にはiOS, Mac等のAppleプラットフォームの開発環境として標準になっていくと思います。
Avatar
回答ありがとうございます。1ヶ月ほど勉強した感想なのですが、Swiftをしらない初学者がいきなりSwiftUIをやるのは、やはり難易度が高いでしょうか。おすすめの勉強法等あれば教えていただきたいです。
Avatar
Kishikawa Katsumi 5/25/2020 8:01 AM
SwiftUIをやりながらSwiftを覚えていけるとも思うのでなんともいえないですが、SwiftUIはまだ既存の開発手法に比べて情報が少ないし、そもそもできないことも多いので作りたいものによっては向いていない恐れはあります。 SwiftUIを勉強したいのか、何か具体的なアプリなどを作りたいのか、Swift言語を勉強したいのか、などによると思います。 はっきりしてなくてもいいのですが、勉強しようと思った目的はどういったものでしょうか?
Avatar
勉強の目的は「自分でiPhoneアプリを作れるようになりたい」という思いからです。普段使っているデバイスに自分好みのアプリが入れられたら最高だなと。また個人開発ができるようになったら、副業として収入を得ることを目標にしています。
Avatar
Kishikawa Katsumi 5/25/2020 8:21 AM
それなら実用的な観点でいうとSwiftUIにこだわらず、とっつきやすそうな本を2、3冊入手して作ってみるのがいいと思います。SwiftUIは必要に応じて、特に何もなければ1、2年後くらいにはもっと情報の入手性も上がってるのでそれくらいでもいいと思います。
Avatar
なるほど。目標を「アプリを作る」に絞ったほうがいいということですね。ネットを見る限り現場の方も手探り状態のようなので、気長に続けようと思います!
8:26 AM
回答ありがとうございました!
Avatar
あと、インターネットにあふれているSwiftUIの情報のかなりのものが古くて、APIが変わっていたりすでに陳腐化した手法だったりするので(AppleのWWDCのドキュメントも含む、紙媒体は特に古い可能性が高い)、かならず最新の情報かどうかを確認してください。 (edited)
12:15 AM
数ヶ月前の情報を見ていると時間をかなり無駄にします。 (edited)
12:17 AM
あと、1ヶ月くらいで(Imaginaryな)WWDCなので、いまは情報や知識が一瞬で陳腐化する可能性が高いです。 (edited)
12:19 AM
これはいつにおいてもそうなのですが、特に今は長期的な知識として体系的に構造を理解して勉強する、表面的なAPIに惑わされないなどの勉強の工夫が必要だと思います。
😻 1
Avatar
まだまだ発展途上ということですね。構造や根底にある考え方を優先して勉強していこうと思います。
10:37 AM
ありがとうございます!
😊 1
Avatar
ページの一部分を横スクロールできるようにしたアプリってよくありますけど( App Store とか Apple TV とか Prime ビデオとか)、そんな感じの UI で横スクロールしたときに Safe Area の外にはみ出して表示したいと考えています(特に iPhone X タイプのノッチがある端末で Landscape のとき)。ただ、普通にやると Safe Area の外はクリップされてしまい、 edgesIgnoringSageArea にすると(そもそも Safe Area がなくなるので)スクロールしてない状態でも最初から Safe Area の外にはみ出てしまうという状況です。 SwiftUI でうまくやるにはどうすれば良いでしょうか?
Avatar
Kishikawa Katsumi 5/27/2020 1:15 AM
Pure SwiftUIでできるかなあ
😂 1
Avatar
うーん、今取り組んでるアプリで SwiftUI で作った方が楽そうな部分があって、意外と SwiftUI ベースで必要なところだけ UIKit 埋め込みでいけないかと考えてたんですが、 UIKit ベースで楽になるとこは SwiftUI 埋め込みの方が現実的そうですね・・・。 (edited)
Avatar
Kishikawa Katsumi 5/27/2020 1:21 AM
ページの一部分を横スクロールはCollectionViewとCompositional Layoutの方がだいぶ簡単だと思います。
Avatar
動くとこまでは SwiftUI でも簡単だったんですけどね😢 Safe Area みたいな細かいところが・・・。
Avatar
Kishikawa Katsumi 5/27/2020 1:25 AM
SwiftUIで細かいところが気になりだすと、だいたいコンポーネントを一から自作することになります 😅
😭 1
1:27 AM
プリミティブはよくできててUIKitより断然使いやすいと思いますが、コンポーネントはだいぶ基本機能が足りない、、、
Avatar
View の再利用とかめちゃくちゃやりやすいですしねぇ。いいところはいいんですが・・・。今回は、 Table View ( List )のセルの操作が結構入りそうな箇所があり、そこを差分計算でやってもらった方が楽そうだなと。そこだけ SwiftUI 埋め込みする方針で検討してみます。
Avatar
Kishikawa Katsumi 5/27/2020 1:33 AM
そこはDiffableDataSourceで。。。UIKitの方がいいと思う。
1:33 AM
たぶん。。
Avatar
UIKitなら全部UIKitでできるよ?
😅 1
Avatar
おおお、 UITableViewDiffableDataSource とかできてたんですね。把握できてませんでした。部分的にでも SwiftUI 使ってみたい気持ちもありますが、これも検討してみます。
Avatar
根っこを SwiftUI で作るのは怖いから、 UIViewController ベースにして、使えそうなところは UIHostingViewConroller に入れて使う形を試してたら、これまで GeometryReader の中にあった NavigationViewUINavigationController として外に出すことになって、そうすると geometry が変化しまくって GeometryReader 以下の再描画が走りまくることに・・・。
👀 2
Avatar
UIHostingViewController 使わずに UIView ベースにするとなんか解決しそうですね...!
6:10 PM
雰囲気、SwiftUI は多分逆を想定してる気がするんだよなあ。
6:11 PM
好きなように migrate できるよてきな言い分をしてたけど。
Avatar
import Combine class Foo: ObservableObject { @Published var a: Int = 0 @Published var b: String = "" static let shared: Foo = .init() } import SwiftUI struct ContentView: View { @ObservedObject var foo: Foo = .shared let cancellable = Foo.shared.objectWillChange.sink { _ in print("change") } var body: some View { print("body") return VStack { Text("a: \(foo.a)") Button("+") { Foo.shared.a += 1 } Text("b: \(foo.b)") Button("+") { Foo.shared.b += "+" } Button("++") { Foo.shared.a += 1 Foo.shared.b += "-" } } } } ↑のようにして、 ++ ボタンを押したときに FooobjectWillChange には 2 回値が流れる( "change" は 2 回表示される)けど、 "body" は 1 回しか表示されない。なんとなく、 @State とか @ObservedObject とかは変更が生じる度に body が実行されるのだと思ってました。当たり前かもしれないけど、ちゃんと setNeedDisplay みたいな仕組みが備わってるんですね。
Avatar
確か元々didSetで通知だったのがwillSetで通知に変わりましたよね
10:33 AM
多分その辺りでsetNeedsDisplay相当の処理になったんじゃないかと考えています
Avatar
なるほど。当時リアルタイムで追ってなかったのでそのあたりの変遷をよく知らないんですよね😅
Avatar
質問です。 read-only な Binding (のようなもの)が欲しくなることってないですか?変更を通知して子ビューに反映させたいけど、子ビュー側から変更してほしくないようなケースで。 set を空にする extension でも書けば良い? あと、 class Foo: ObservableObject があったときに、 @ObservedObject var foos: [Foo] 的なことしたくなりませんか? @ObservedArray とかがあればいいのかな? もしくは、僕が知らないだけで、↑を実現する方法が標準で提供されていたりしますか?軽くググっても見つけられなかったんですが・・・。
Avatar
@Binding なのを見せないでなにか違うものを見せるのかなあ?
Avatar
とりあえず↓みたいなものを作ったらできたのはできたんですが、 import SwiftUI extension Binding { static func readOnly<Value>(_ binding: Binding<Value>) -> Binding<Value> { .init(get: { binding.wrappedValue }, set: { _ in }) } }
10:20 PM
そもそも ObservableObject の方で↓みたいに read-only にしたい場合は、 $foo.a みたいにして Binding が取れないから不便ですね・・・。 class Foo: ObservableObject { @Published private(set) var a: Int = 0 ... }
10:22 PM
@ObservedObjectprojectedValue 以外の read-only のための API を作って、 _foo.readOnly.a みたいに取得できるようにすればいいのかな。 (edited)
Avatar
できた。 import SwiftUI extension ObservedObject { var readOnly: ReadOnlyWrapper { .init(self) } @dynamicMemberLookup struct ReadOnlyWrapper { private let object: ObservedObject<ObjectType> init(_ object: ObservedObject<ObjectType>) { self.object = object } subscript<Subject>(dynamicMember keyPath: KeyPath<ObjectType, Subject>) -> Binding<Subject> { Binding(get: { self.object.wrappedValue[keyPath: keyPath] }, set: { _ in assertionFailure("Read-only") }) } } }
10:36 PM
あー、 WrapperreadOnly 生やして $foo.readOnly.a みたいな方がいいか。
Avatar
だめだ。 KeyPathWrapper のための ReferenceWritableKeyPath に変換できない。 import SwiftUI extension ObservedObject.Wrapper { var readOnly: ReadOnly { .init(self) } @dynamicMemberLookup struct ReadOnly { private let wrapper: ObservedObject<ObjectType>.Wrapper init(_ wrapper: ObservedObject<ObjectType>.Wrapper) { self.wrapper = wrapper } subscript<Subject>(dynamicMember keyPath: KeyPath<ObjectType, Subject>) -> Binding<Subject> { get { wrapper[keyPath: keyPath] } // ⛔ set { assertionFailure("Read-only") } } } }
10:49 PM
Wrapper がラップしてる元のオブジェクトを参照できればいいんだけど・・・。
10:50 PM
あ、 subscriptget, set じゃなくて getBindingset をつぶさなきゃいけないか。どっちにしろダメだけど。 (edited)
10:53 PM
WrapperreadOnly 生やして $foo.readOnly.a は諦めて、少々ブサイク(?)だけど ReadOnlyWrapper 方式で _foo.readOnly.a にするか。 (edited)
Avatar
foo.a.readonlyはどうですか? Bindingから弱める
Avatar
$foo.a.readOnly やんね?たしかにそれならできるかも。
Avatar
あー、それ最初に検討したけどダメだったんだ。 a が read-only のときにそもそも $foo.a が作れないんだった。
2:53 PM
.lazy とのアナロジーで考えるとやっぱ Wrapperreadonly 付けたい気がするなぁ。大元に付けることで下流の性質が変かする。まあ、 _foo.readOnly でも同じなのかもしれないけど。
Avatar
$foo.readOnly.a できた。この方法絶対ダメだけどw import SwiftUI extension ObservedObject.Wrapper { var readOnly: ReadOnly { let object: ObjectType = unsafeBitCast(self, to: ObjectType.self) return ReadOnly(object) } @dynamicMemberLookup struct ReadOnly { private let object: ObjectType init(_ object: ObjectType) { self.object = object } subscript<Subject>(dynamicMember keyPath: KeyPath<ObjectType, Subject>) -> Binding<Subject> { Binding<Subject>( get: { self.object[keyPath: keyPath] }, set: { _ in assertionFailure("Read-only") } ) } } }
3:04 PM
Wrapper はどう考えても元のオブジェクトをラップしてるだけだろうと考えて unsafeBitCast したら動いたw
3:10 PM
今やりたいのは、↓の Foo のような型に対して @ObservedObject var foo: Foo を作って、 $foo.readOnly.a のようにして read-only な片方向のバインディングを作りたいということです。 import Combine class Foo: ObservableObject { @Published private(set) var a: Int = 0 func incrementA() { a += 1 } }
3:12 PM
今は incrementA が露出してるけど、アップデートのための関数は生成者や internal にしか暴露されないとかもあり得る。そうでなくても、↑の incrementA のようにアップデートの方法を限定したいからプロパティが private(set) になるケースもあって、そのようなケースでもバインディングしたいというニーズも考えられる。
Avatar
もっといい方法があるかもしれないから聞いてみた。 https://forums.swift.org/t/one-way-data-binding-from-observedobject-with-swiftui/37547
Is it possible to achieve one-way data binding with SwiftUI in the situation like below? import Combine final class Counter: ObservableObject { @Published private(set) var count: Int = 0 func increment() { count += 1 } func reset() { count = 0 } } import SwiftUI...
Avatar
あああ、根本的に勘違いしてた。こんなことする必要ないのか・・・。当たり前だ・・・。
4:35 PM
If it's read-only, you should pass it as normal let value
https://forums.swift.org/t/one-way-data-binding-from-observedobject-with-swiftui/37547/2 これに気付かなかったのアホすぎる・・・。
4:37 PM
なんで気付かなかったんだろう。
Avatar
omochimetaru 6/14/2020 3:24 AM
え、これは受け側で書き込まないように自粛してるだけの書き方ではないですか?
3:24 AM
やりたいのは渡す側で書き込み禁止を表明して、静的に安全にしたいのではなく?
Avatar
受け側( NumberDisplay )は let number: Int で受け取るから書き込みできなくない?
Avatar
omochimetaru 6/14/2020 3:34 AM
はい。やりたいことはそうではなくて
3:35 AM
渡す側(counter.countって書く側)で、書き込めない型にする事だと思ってました
Avatar
Int は書き込めない型じゃないかな?
3:36 AM
Binding<Int> は書き込めるけど。
3:37 AM
ああ、日本語のパースにミスった。
3:37 AM
「(渡す側で書き込めない)型にする」か (edited)
3:37 AM
「(渡す側で)(書き込めない型にする)」かと思った。
Avatar
omochimetaru 6/14/2020 3:37 AM
それは確かにそうだけど、それはただ値を渡してるだけで、 今度は、変更通知が伝わらない
Avatar
いや、伝わるよ。
Avatar
omochimetaru 6/14/2020 3:38 AM
読める型、読み書きできる型、変更通知の3つの機能のうち、読めるけど変更通知できる2つだけほしいのですよね
Avatar
ContentView@ObservedObject として counter を保持しているから、
3:38 AM
counter が変更されたら ContentViewbody が走って、
3:38 AM
NumberDisplay(counter.count) が作り直されて、
3:39 AM
NumberDisplay も更新される。
3:39 AM
仮想ビューだから作り直す前提だからそれで問題ない。
Avatar
omochimetaru 6/14/2020 3:42 AM
あれ、SwiftUI的にはそれでいいのか。 社内チャットで書いていた↓でいうところの
仮にストア自体を ObservableObject にしても、個々の値を監視したいことはあるから、↑の Store が ObservableObject なだけではダメなんよね。 関係のない更新が走りまくってしまう。
「関係のない更新が走りまくってしまう」に該当しているけど、それはいい?
(edited)
Avatar
N:1と1:1は問題を切り分けた方がいい気がする。
3:47 AM
もし ContentView@Published var numbers: [Int] を持つ ObservableObject@ObservedObject で保持していて、
3:48 AM
そのうちひとつだけを NumberDisplay(numbers[0]) とかで表示していると良くなさそう。
3:49 AM
でも、 numbers 全体を表示してる( ContentViewnumbers.count 個の NumberDisplay を表示している)なら必要な更新だから問題ないと思う。
Avatar
omochimetaru 6/14/2020 3:57 AM
もしCounterが class Counter: ObservedObject { @Published var count: Int @Published var foo: Foo } のようにfooも持っていてもこれはclassだから分離されているから良いけど・・・ (edited)
3:59 AM
class Counter: ObservedObject { @Published var count: Count } struct Count { var value: Int var foo: Foo } (edited)
4:00 AM
のような場合にcount.valueが変わると、これはcount自体の変更になって、 count.fooの更新通知も発生してしまうのが嫌だよねという話かと思ってたけど
4:01 AM
これはもうstructの部分更新が全体更新と同一視されるのは基本的な事だからそれでいいのかな。
4:01 AM
この状況において $counter.count.value でバインディングしたときに、$counter.count.foo でバインディングしている側には影響が無いようなシステムを期待しているイメージだった。
4:02 AM
SwiftUIの場合は実装的には、$counter.count.foo をバインドしてるView Tree側で、勝手に変更チェックをしてfooが変化していなければ実View(FooView)の更新はしないという事をやるのだと思うけど。 (edited)
Avatar
どちらにせよ親側(ContentView)が@ObservedObjectでCounterを保持している時点で、valueが変わってもfooが変わっても親側のbodyから再生成なんよね。 (edited)
Avatar
omochimetaru 6/14/2020 4:08 AM
あら、違うかな、 そのときに $counter.count.foo が書き込みインターフェースを提供しない型になるというのがやりたいことだったけど、 counter.count.foo ($を外す) を使えばいいよねという話で解決したという事か
4:09 AM
@ObservedObject var counter で掴んでる根っこのところから、毎回出発するからそれはそうなのか。
Avatar
うん、そうなんよね。なんでそれに気付かなかったのか…。仮想ビューの考え方にちゃんと頭の切り替えができてなかった。
Avatar
omochimetaru 6/14/2020 4:16 AM
う〜むなるほど。僕もわかってなかった😅
4:17 AM
なんかproperty wrapperと通知の仕組みがあるからそこに動的なインスタンスがある事を意識してしまうけど、セマンティクスとしては普通のvarなのが難しいな・・・
Avatar
言われてみれば当たり前だけど発想の転換が必要だったね。もう少し慣れねば💦
Avatar
シンプルなリマインダーアプリを作ってるんですが、Listについてわからないことがあります。「キーボードを表示しているときに、適当なところをタップするとキーボードを閉じる」という動作を実装したいんですが、この動作をできるようにすると、List編集中のDeleteが効かなくなってしまいます。
1:43 PM
List { ForEach(text, id: \.self) { user in Text(user) }.onDelete{ offset in    self.text.remove(atOffsets: offset) }.onMove{ source, destination in self.text.move(fromOffsets: source, toOffset:destination) } } .onTapGesture { UIApplication.shared.endEditing() } (edited)
1:45 PM
extension UIApplication { func endEditing() { sendAction( #selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil ) } } (edited)
1:48 PM
よければ原因を教えていただけると助かります、、
Avatar
cornerRadiusモデファイア氏、内容がネガティブパディング付いているとわかりやすいですが、勝手に子Viewにclip掛けちゃうんですね… (edited)
7:32 AM
7:32 AM
7:32 AM
7:38 AM
cornerRadiusの内部的には、返すViewを生成するときに、clipShape()とか、またはRoundedRectangle作ってclip()みたいなのと同じようなことが行われているのかな
Avatar
これはUIKitでも同じ事が起きますね。大枠の仕組みは変わってないのだと思います
🍵 1
😴 1
🤔 1
Avatar
UIKitだとこんな感じで clipsToBoundsmasksToBounds がデフォルト false なので cornerRadius を掛けたとしてもちゃんとはみ出すので、それと同じノリでSwiftUIでもはみだしてくれるやろと雑に突撃して撃沈した感じです😇 (↑のSwiftUIに条件を揃えたpaddingによるはみ出しではないですが) (edited)
10:47 AM
(いちおうSwiftUIでも条件を揃えてpaddingではなくFrameではみ出させてもClipする) (edited)
Avatar
import SwiftUI struct ContentView: View { @ObservedObject var counter: Counter = .shared var body: some View { print("ContentView.body") return NavigationView { VStack { HStack { Text("\(counter.count)") Button("+") { self.counter.increment() } } NavigationLink(destination: AView(count: counter.count)) { Text("Open A") } } } } } struct AView: View { let count: Int var body: some View { print("AView.body") return VStack { HStack { Text("\(count)") Button("+") { Counter.shared.increment() } } NavigationLink(destination: BView(count: count)) { Text("Open B") } } } } struct BView: View { let count: Int var body: some View { print("BView.body") return HStack { Text("\(count)") Button("+") { Counter.shared.increment() } } } } final class Counter: ObservableObject { @Published private(set) var count: Int = 0 func increment() { count += 1 } static let shared: Counter = .init() }
4:04 AM
@ObservedObject の影響範囲を調べてて、↑で AView を開いてから + ボタン押しても変更が反映されるのがおどろきだったんですが、さらに BView を開いて + ボタンを押しても変更は反映されないし、そこから Back して AView に戻ってから + ボタンを押しても今度は変更が反映されないんですが、挙動がおかしくないですか? (edited)
4:06 AM
最初に AView を開いたときに変更が反映されるのがおかしいのか、それとも AView に戻ってきたときや BView で変更が反映されないのがおかしいのか。
Avatar
omochimetaru 6/25/2020 4:12 AM
AView, BView は Counterについての依存が見えない定義になっているから、ボタンを押しても何も起きないのはわかるけど
4:12 AM
最初に開いた後にボタンを押すと反応するのが不思議ですね
4:13 AM
ContentView.body の再描画が起こりつつ NavigationLink の遷移状態が継続しててAViewが再描画されてそう (edited)
4:14 AM
Navigationに関する一つ前までがステートツリーとして監視されているという実装だったとしたら現象の説明がつきそう。
4:14 AM
左上に前の画面のタイトルが表示されているから・・・
4:14 AM
で、そこは 前の画面の .navigationTitle() modifierだから
4:15 AM
一個前と今の画面だけがステート監視されてるみたいな。
4:16 AM
AからBに遷移した後は 根っこのContentView のステートは捨てられてるからCounter.countがイベントを発行してもBの再描画が起きない。
4:17 AM
なにがおかしいのかでいうと、 Counter.shared ってSwiftUIシステムから感知できない形で依存性をもってしまっている事じゃないですかね
4:18 AM
SwiftUI的には前提が壊れるからあらゆるバグが起きてもヨシという扱いだと思います。
Avatar
なにがおかしいのかでいうと、 Counter.shared ってSwiftUIシステムから感知できない形で依存性をもってしまっている事じゃないですかね
えーでも、Counter自身はObservableObjectだし、countの変化がSwiftUI以外の要因(サーバー側から変化が送られてくるとか)パターンを考えると別に Counter.shared はそれをエミュレートしてるのと変わらない気がするので、納得いかないです
Avatar
omochimetaru 6/25/2020 4:53 AM
Counter.shared をいきなり触るんじゃなくて、 AView と BView に @ObservedObject として引数で渡すとか、 @EnvironmentObject だっけ? として渡さないといけないのでは
Avatar
うーん、AViewもBViewもコンストラクタで渡されたものを表示するだけの静的なコンポーネントと考えると、その前提もおかしい気はするんですよね。これってNavigationLinkがなかったら、たぶんAViewもBViewも更新されるはずですよね。
4:57 AM
NavigationLinkの代わりに、ContentViewの中に子供としてAViewがあり、AViewの中に子供としてBViewがある場合。
Avatar
omochimetaru 6/25/2020 4:57 AM
表示するだけじゃなくて、クリックして外界に影響を与えるので
4:57 AM
その表明が必要じゃないかなと
4:58 AM
その表明が無い事によって、SwiftUIエンジンが、状態更新の起点を見逃してしまうという挙動
Avatar
クリックというのはナビゲーションのこと?カウンターをあげる方のこと?
Avatar
omochimetaru 6/25/2020 4:58 AM
ああ、そっか2つあるのか。カウンターをあげる作用のことです。
Avatar
NavigationLinkの代わりに、ContentViewの中に子供としてAViewがあり、AViewの中に子供としてBViewがある場合。
この場合、BViewも更新されるんちゃうんと勝手に思っていたけど、実際どうなんだろう。子供でもBViewが更新されないのなら納得はいきますね。たしかに。
Avatar
omochimetaru 6/25/2020 5:01 AM
子供でも同じじゃないですかね 変わってくるのはAViewとかBViewの型をつくらずにそのbodyの中身を書き下した場合。
Avatar
import SwiftUI struct ContentView: View { @ObservedObject var counter: Counter = .shared var body: some View { print("ContentView.body") return NavigationView { VStack { HStack { Text("\(counter.count)") Button("+") { self.counter.increment() } } AView(count: counter.count) } } } } struct AView: View { let count: Int var body: some View { print("AView.body") return VStack { HStack { Text("\(count)") Button("+") { Counter.shared.increment() } } BView(count: count) } } } struct BView: View { let count: Int var body: some View { print("BView.body") return HStack { Text("\(count)") Button("+") { Counter.shared.increment() } } } } final class Counter: ObservableObject { @Published private(set) var count: Int = 0 func increment() { count += 1 } static let shared: Counter = .init() } struct ContentView_Previews: PreviewProvider { static var previews: some View { ContentView() } }
5:05 AM
子供だと、どの + を押しても全部更新されました。 (edited)
Avatar
omochimetaru 6/25/2020 5:06 AM
なるほど・・・
Avatar
やっぱりNavigationがどうも怪しい気がする
Avatar
omochimetaru 6/25/2020 5:06 AM
動く側に倒れるのは嫌だな・・・
Avatar
AViewが作り直されるなら、そのbodyがイニシャライザの引数の違うBViewを作って返している以上、BViewの部分の表示は変わって欲しいんですけどね。
5:09 AM
あ、その前に、ContantViewが作り直されるならそのbodyがイニシャライザの引数の違うAViewを作って返している以上、AViewも作り直されて欲しい、を言うのが抜けてた。
5:15 AM
+ボタンはいったんなしで考えて(外部要因でCounterが更新されるとして)、その上で、AViewやBViewが直接Counterとは関係ないものを考える。例えばBViewは任意の文字列の先頭数文字を表示するという機能を持つViewで、AViewは任意の文字列の先頭を表示しつつ、なにか装飾を加えて表示するViewだというような汎用コンポーネント。すると、それを利用するContentViewのみ、Counterを見て表示を更新してる、というのは自然だと思うんですよね。
Avatar
omochimetaru 6/25/2020 5:17 AM
そのAViewとBViewにわたす元になる文字列を変更する動作の起点となるUIイベント(クリックハンドラ等)が ContentViewの中にあるのであれば、そのハンドラの中から触るのはContentViewの@EnvironmentObject 等に限定されているべきで、一般のグローバル変数は触ってはいけないんじゃないかなあ。
Avatar
ContentViewはObservableObjectのCounterをObservedObjectで見てるから、UIイベントがなくても、CounterのobjectWillChangeが発火すれば、作り直されるべきだと思うんですよね。
Avatar
omochimetaru 6/25/2020 5:21 AM
なるほど。そこは、そうですね。
Avatar
.sharedってなってるからグローバル変数感がありますが、Counterのインスタンスを作って、それがタイマーとか、サーバーからの通知とか、そういうのでobjectWillChangeが発火するイメージ
Avatar
omochimetaru 6/25/2020 5:23 AM
AViewとBViewがContentViewの子になってるときは、それでうまくいってるという事か。
Avatar
そうですね。
Avatar
omochimetaru 6/25/2020 5:23 AM
やっぱりNavigationViewが出てきて画面遷移がBまで行くと、ContentViewのobjectWillChangeの購読が中止されてそう
Avatar
画面遷移がAの時点でContentViewの方は変化しなくなるのならNavigationとはそういう挙動として納得できるのだが。 (edited)
Avatar
omochimetaru 6/25/2020 5:24 AM
画面遷移後も objectWillChange によって見た目が更新されて欲しければ、AViewもCounterを受け取るべきということかな
5:25 AM
Aの時点で更新される挙動はポリシーが一貫してない結果だけど
5:26 AM
僕はそこは怪しい挙動に突入しているのであってAViewが自分で購読するのが正しい書き方という規約なんじゃないかと思います
Avatar
実はぼくもちょっと前に、画面遷移したら前の画面の更新が次の画面に伝わったりはしない方を期待してたんですよ
Avatar
omochimetaru 6/25/2020 5:28 AM
もしBViewが @EnvironmentObject でCounterを参照していたとしても、このパターンだとやっぱり更新されない気がしてきた BViewにとってのcountがletなので。
5:29 AM
BView.bodyが @EnvironmentObject 経由で値を見ているべきで、 ContentView → AView → BView の過程の値の加工パスは前提にしちゃいけないような気がする
Avatar
えーそれだと、「BViewは任意の文字列の先頭数文字を表示するという機能を持つView」のような再利用可能なコンポーネントが常にEnvironmentObjectかBindableを受け取らないといけなくなっちゃう気がする。
Avatar
omochimetaru 6/25/2020 5:31 AM
画面遷移の場合はそうってことになるんじゃないかな
Avatar
ああ、画面遷移の場合は、っていう条件付きで、ですね
Avatar
omochimetaru 6/25/2020 5:32 AM
子の場合は親のbodyの再描画によって書き換わって再利用できるからOK
Avatar
そう。伝わらないこと前提がいいんですが、伝わるパターンがあるのが困る。
Avatar
omochimetaru 6/25/2020 5:32 AM
だから、「画面として機能させたい」のかどうかという意識をする必要が出てくるのかな。
5:32 AM
設計する上で。
5:33 AM
伝わるパターンがあるのはSwiftUIが壊れてるのかプログラマが規約違反してるのかはっきりしないから嫌ですね。
Avatar
さっき書きかけたんですが、
5:34 AM
X画面(XViewと、それがObservedObjectで見るXViewModel)とY画面(YViewとYViewModel)を独立して作ってて、X画面からY画面へNavigationLinkで遷移するようにしてたんですね
5:36 AM
で、XViewModelがまあまあ無駄にメモリ食う感じのするものを持っていたので、XViewが表示されてるとき以外はXViewModelの中でそれを持つのをやめようと思って、XViewのonDisappearのタイミングでXViewModelのそのでかいやつを消すようにしたんです
5:37 AM
そしたら、X画面からY画面へ遷移したあとにXViewのonDisappearが呼ばれて(ここまでは想定通り)、それによりXViewModelのPublishedな変数を更新したら、X画面も作り直されて、その結果、NavigationLinkのある部分が消えて、Y画面にいることができなくなり、X画面へ戻るという挙動に
Avatar
omochimetaru 6/25/2020 5:38 AM
前画面のNavigationLinkが消えると次画面から前画面に引き戻される?
Avatar
それを見て、あー、画面遷移しても、前画面は全部生きてるのかー、根本的に認識を間違えてたわーと思ってたとこだったんで。
5:38 AM
そう、戻ります。
Avatar
omochimetaru 6/25/2020 5:39 AM
なるほど。合わせると、3画面目で1画面目のLinkが消えるロジックが入ってても1画面目の再描画が走らなくて大丈夫みたいな事が起こりそうですね。 (edited)
Avatar
それは困るんですよね。認識しづらい。
5:40 AM
どっちかにカチッと決まってくれないと
Avatar
omochimetaru 6/25/2020 5:41 AM
そうですねえ。
5:41 AM
やっぱりライブラリとしての抽象度が高すぎるんだよな〜 (edited)
Avatar
あれでも、1画面目のNavigationLinkのisActiveを、EnvoronmentObjectに持たせた変数にBindしておいて、3画面目でその変数をfalseにしたら1画面目まで一気に戻れるぞ、というのをこのまえ確認したんだけどなw
Avatar
omochimetaru 6/25/2020 5:43 AM
ww
Avatar
確認したつもりでしたが、今やってみると動きませんでしたw あれれw
5:52 AM
2画面目までしかやらなかったのかな。
Avatar
omochimetaru 6/25/2020 5:54 AM
動かないのであれば、一貫性?はありますね。
Avatar
いやな一貫性だw
🦴 1
6:00 AM
struct ContentView: View { var body: some View { print("ContentView.body") return NavigationView { XView() } .environmentObject(Env()) } } struct XView: View { @EnvironmentObject var env: Env var body: some View { NavigationLink(destination: YView(), isActive: $env.isRootNavitaionActive) { Text("To Y") } .navigationBarTitle("X") } } struct YView: View { @EnvironmentObject var env: Env var body: some View { VStack { NavigationLink(destination: ZView()) { Text("To Z") } Button(action: { self.env.isRootNavitaionActive = false }) { Text("Tap this") } } .navigationBarTitle("Y") } } struct ZView: View { @EnvironmentObject var env: Env @State var text: String = "(Before tap)" var body: some View { VStack { Text(text) Button(action: { self.text = "(After tap)" self.env.isRootNavitaionActive = false }) { Text("Tap this") } } .navigationBarTitle("Z") } } final class Env: ObservableObject { @Published var isRootNavitaionActive = false }
6:00 AM
うん。2画面目だと戻るけど、3画面目だと戻らない。一貫性があります 😢
Avatar
omochimetaru 6/25/2020 6:03 AM
試してみた ZViewでtap thisおすとXまで戻りますよ
Avatar
うそーん。。。
Avatar
omochimetaru 6/25/2020 6:04 AM
6:04 AM
プロジェクト新規作成して↑のコードをコピペ。
Avatar
もしかしてiOS 14だと戻ったりする?
Avatar
omochimetaru 6/25/2020 6:05 AM
6:05 AM
iOS13だとビルドできない・・・
6:06 AM
あ、古いXcodeでやってみればいいか
6:08 AM
お。
6:08 AM
iOS13.5だと、Zからは戻らないですね。
Avatar
も、もしかしてiOS 14だとkoherさんのやつもBViewまで更新されるのでは
6:10 AM
試してみました。更新されますね
Avatar
omochimetaru 6/25/2020 6:10 AM
僕も確認できました。 (edited)
6:11 AM
これはどうなんだ・・・
Avatar
えーそんなんアプリ作れないよー
Avatar
omochimetaru 6/25/2020 6:11 AM
ここまでの話はもはや空論で、iOS14では、Navigationスタックを全部くしざして
6:11 AM
ちゃんと根本のNavigationViewのあるところから、再描画されるという
6:11 AM
仕様だと思ってしまっていいのか??
Avatar
まあそれが仕様なら納得はできる仕様です。2画面目までだけとかじゃないから。
Avatar
omochimetaru 6/25/2020 6:13 AM
そうですね。
Avatar
ただ、iOS 13ではそう動かないのだと、ちゃんとしたアプリを作るにはちょっと困るw こういう確認して遊んでる分にはいいですが
Avatar
omochimetaru 6/25/2020 6:14 AM
SwiftUIはiOS14が普及してから使いましょうという話になってしまうな。
Avatar
去年6月 よーしiOS 13が普及してから使うぞ。2年後だ! 今年6月 よーしiOS 14が普及してから使うぞ。2年後だ!
👆 1
Avatar
omochimetaru 6/25/2020 6:15 AM
完全にデジャブ
6:16 AM
一応iOS13.6で挙動が揃うとかはありえるか。
Avatar
お、そうですね。
Avatar
omochimetaru 6/25/2020 6:17 AM
13.6なら入れてもいいけど14は見送る人はまあまあ居そう。
Avatar
Xcode 11.6でやってみます
6:18 AM
ダメでした 😭 (2画面目までしか伝わらない) (edited)
😩 1
Avatar
見てない&ご飯食べてる間にすごい伸びてた💦
Avatar
yutailang0119 6/25/2020 6:21 AM
SwiftUI 2.0はiOS 14+です
Avatar
omochimetaru 6/25/2020 6:21 AM
1.0到達してるのかしら・・・
Avatar
yutailang0119 6/25/2020 6:23 AM
SwiftUI 5くらいまでは、毎年メジャーリリース
Avatar
やっと読み終わった。
6:27 AM
これ、おもしろいことがいくつかあって、
6:28 AM
BView でボタン押すと count は増えないけど、 ContentView.body は発火してるんですよね。
6:28 AM
print で確認できる。
6:29 AM
あと、 AViewcount プロパティを作らなかったら、変更が反映されない。これは理解できる。
Avatar
まあiOS 14の挙動なら理解できるので、iOS 14が来たら起こしてください…
😴 1
😂 1
Avatar
count プロパティがないと View の差分がないので AView の再描画が走らないのかと。
Avatar
omochimetaru 6/25/2020 6:33 AM
なんかそれは言われてますよね
6:33 AM
bodyの再生成と、再描画の間に、diffチェックが有る
Avatar
NavigationLink の場合、クロージャではなく引数に直接 AView(count: counter.count) が渡されてるから、そこでつながってるのも理解できる。
6:34 AM
ContentView が再描画されるんだったら(これは iOS 13 & BView からでも確認済み)、 AView が新しい count で作られて、差分が発生するから再描画されてもおかしくない。
6:35 AM
二段階になったら更新が反映されなくなるのは、 iOS 14 の挙動を聞いてる限り、 iOS 13 のバグなのかなぁ。
6:36 AM
navigation 遷移したらそこで途切れるという仕様でもいいと思うけど・・・。
Avatar
omochimetaru 6/25/2020 6:36 AM
フォーラムで聞くといいのかも?
Avatar
今だと Lab とかがいいのかな?
Avatar
omochimetaru 6/25/2020 6:36 AM
#swiftpm のほうで rintaro さんが
6:37 AM
Avatar
なるほど。
Avatar
omochimetaru 6/25/2020 6:37 AM
swift forumじゃなくて apple developer forum
Avatar
あー、なるほど。
6:38 AM
この前、 Swift Forums でも数分で返ってきたけどね。あのやらかした恥ずかしい質問のやつ。
Avatar
omochimetaru 6/25/2020 6:40 AM
iOS13と14の違いをAppleがどういうつもりでやってるのかはAppleの方のフォーラムのほうが回答あるかなと思って。
Avatar
そうかも。
Avatar
bodyが返したものがツリーになって裏で仮想DOM的に保持されているはずだから、その仮想DOMツリーを表示するデバッガが欲しいですね。実際のView Hierarchyみたいな感じのやつで。
Avatar
そもそもiOS13のSwiftUIはマイナーバージョンアップでも動きが変わる感じでunstable過ぎたので
Avatar
struct ContentView2: View { @ObservedObject private var counter: Counter = .shared var body: some View { print("ContentView2.body") return VStack { HStack { Text("\(counter.count)") Button("+") { self.counter.increment() } } AView() .padding() .border(Color.gray, width: 1) .padding() } } } private struct AView: View { var body: some View { print("AView.body") return VStack { HStack { Text("\(Counter.shared.count)") Button("+") { Counter.shared.increment() } } BView() .padding() .border(Color.gray, width: 1) .padding() } } } private struct BView: View { var body: some View { print("BView.body") return HStack { Text("\(Counter.shared.count)") Button("+") { Counter.shared.increment() } } } } private final class Counter: ObservableObject { @Published private(set) var count: Int = 0 func increment() { count += 1 } static let shared: Counter = .init() } ↑これだと同一画面だけど AViewBView は更新されない( iOS 13 )。おもしろい。 (edited)
Avatar
14でマトモになるなら13の 下位互換は正直なくてもいいやの感ある (edited)
Avatar
これは更新されなくていいと思うんですよね。AViewやBViewの更新を発火するものがないから。
Avatar
はい、そう思います。
6:50 AM
AView に diff がないから body の発火まで行かないのかと。 (edited)
Avatar
何を基準にdiffのあるなしを見てるんでしょうね。
Avatar
でもなんか変な気もしてきたな。これって AViewBView を展開して ContentView2 に埋め込んだ場合は更新されますよね?
Avatar
bodyが呼ばれるかどうかじゃないですかね。
Avatar
SwiftUI って仮想ビューは軽量だから再描画には問題なくて、ツリー全体を再描画した上で diff を見て変化があった部分だけ本物の UI に反映するよって話ですが、
Avatar
AViewやBViewのイニシャライザは呼ばれているけど、AViewやBViewのbodyが呼ばれてないのだと思います。
Avatar
そこに二段階あって、 body が発火されるかと、 body を発火した上で diff があるかがあるってことですよね。
6:55 AM
はい、 body が呼ばれてないんですが、 body が呼ばれるかどうかの判断に、ツリー全体じゃなくて AView としての比較が入ってるってことですよね。
Avatar
仮想DOMの先駆者であるReactでもそれは同じで、ViewにあたるのがComponent、bodyにあたるのがrender()なんですが、そのrender()を呼ぶかどうかを軽量に決める、つまり、前状態とのdiffをとるメソッドがrenderとは別にComponentにあるんですよ。 (edited)
6:57 AM
ViewがEquatableならわからないでもないんですが、Viewってそうでもないから、いったい何を基準にdiffをとってるんだろうと。
Avatar
omochimetaru 6/25/2020 6:57 AM
リフレクションで動的にデータ構造を取得してそうです
Avatar
うーん、たしかに。しかも、 Equatable でないプロパティ持ってることもありますよね・・・。
Avatar
omochimetaru 6/25/2020 6:57 AM
言語ユーザーには提供されてないけどバイナリレベルでは安定仕様になったので。
6:59 AM
Equatableでないものや規定のpriperty wrapperじゃないものは無視するか適当にやってそう
Avatar
Equatable== は関係なさそう。
7:03 AM
import SwiftUI struct ContentView2: View { @ObservedObject private var counter: Counter = .shared var body: some View { print("ContentView2.body") return VStack { HStack { Text("\(counter.count)") Button("+") { self.counter.increment() } } AView(value: .nan) .padding() .border(Color.gray, width: 1) .padding() } } } private struct AView: View { let value: Double var body: some View { print("AView.body") return VStack { HStack { Text("\(Counter.shared.count), \(value)") Button("+") { Counter.shared.increment() } } BView(value: value) .padding() .border(Color.gray, width: 1) .padding() } } } private struct BView: View { let value: Double var body: some View { print("BView.body") return HStack { Text("\(Counter.shared.count), \(value)") Button("+") { Counter.shared.increment() } } } } private final class Counter: ObservableObject { @Published private(set) var count: Int = 0 func increment() { count += 1 } static let shared: Counter = .init() }
7:03 AM
↑では AViewBViewvalue を持っていて、そこに .nan を渡してるから
7:03 AM
== で比較すると常に false になるんだけど再描画されない。
Avatar
.nan で常にfalseにする技だw
Avatar
omochimetaru 6/25/2020 7:04 AM
謎の方法でww
Avatar
もちろん、 valueDouble(counter.count) を渡したら再描画される。
Avatar
いやちょっとまってw その前にAViewとかBViewとかはEquatableじゃないから == で比較できます?
Avatar
プロパティをリフレクションで見て Equatable なものを比較してたらという仮説に対してですね。
Avatar
あ、いや、じゃなくて、本当に Equatable になってませんよ、って言ってます (edited)
Avatar
omochimetaru 6/25/2020 7:06 AM
Double型はEquatableという意味では
Avatar
ああそういう意味でか。
Avatar
はい、そういう意味でした。
Avatar
ぼくはAView自体をEquatableにしたらどうなんだろうと思いました。
Avatar
ちなみに、 AViewBViewEquatable を付けてみても結果は変わりませんでした。
Avatar
なるほどw
Avatar
omochimetaru 6/25/2020 7:07 AM
ビット比較してるんかなあ?
Avatar
ビット比較の可能性あるね。
Avatar
逆にそれしかなさそうな気がしてきた。参照型でもないし。
Avatar
omochimetaru 6/25/2020 7:08 AM
==はtrueになるけどビットは違う型と、 ビットは同じだけど==はfalseになる型を
7:08 AM
用意して実験する?
Avatar
AView(value: Bool.random() ? 1 : 2)
7:08 AM
↑これにすると更新されたりされなかったりでおもしろい。
😂 1
7:12 AM
import SwiftUI struct ContentView2: View { @ObservedObject private var counter: Counter = .shared var body: some View { print("ContentView2.body") return VStack { HStack { Text("\(counter.count)") Button("+") { self.counter.increment() } } AView(value: Integer(42)) .padding() .border(Color.gray, width: 1) .padding() } } } private struct AView<Value: CustomStringConvertible>: View { let value: Value var body: some View { print("AView.body") return VStack { HStack { Text("\(Counter.shared.count), \(value.description)") Button("+") { Counter.shared.increment() } } BView(value: value) .padding() .border(Color.gray, width: 1) .padding() } } } private struct BView<Value: CustomStringConvertible>: View { let value: Value var body: some View { print("BView.body") return HStack { Text("\(Counter.shared.count), \(value.description)") Button("+") { Counter.shared.increment() } } } } private final class Counter: ObservableObject { @Published private(set) var count: Int = 0 func increment() { count += 1 } static let shared: Counter = .init() } final class Integer: Hashable, CustomStringConvertible { let value: Int init(_ value: Int) { self.value = value } static func == (lhs: Integer, rhs: Integer) -> Bool { lhs.value == rhs.value } func hash(into hasher: inout Hasher) { value.hash(into: &hasher) } var description: String { value.description } }
7:13 AM
↑これでも再描画されない。どうなってるんだ??
7:13 AM
参照型の場合はインスタンスのビット単位で比較してんのかな。アドレス関係なく。
Avatar
omochimetaru 6/25/2020 7:14 AM
参照型なら中身かもですね
Avatar
参照型の場合(だけかわからないけど) Equatable を見てるかも?
Avatar
omochimetaru 6/25/2020 7:15 AM
==の中身をtrueにしたら
7:16 AM
わかるかも
Avatar
final class Integer: Hashable, CustomStringConvertible { let value: Int init(_ value: Int) { self.value = value } static func == (lhs: Integer, rhs: Integer) -> Bool { true } func hash(into hasher: inout Hasher) { 42.hash(into: &hasher) } var description: String { value.description } } に変えて、 AView(value: Integer(Bool.random() ? 1 : 2)) にしたら更新されなくなった。
7:16 AM
Integer 変更前は AView(value: Integer(Bool.random() ? 1 : 2)) で更新された。
7:17 AM
参照型はビット比較するとアドレスが異なる場合に diff 発生してしまうから Equatable を見てる??
7:22 AM
import SwiftUI struct ContentView2: View { @ObservedObject private var counter: Counter = .shared var body: some View { print("ContentView2.body") return VStack { HStack { Text("\(counter.count)") Button("+") { self.counter.increment() } } AView(value: Box<Double>(.nan)) .padding() .border(Color.gray, width: 1) .padding() } } } private struct AView<Value: CustomStringConvertible>: View { let value: Value var body: some View { print("AView.body") return VStack { HStack { Text("\(Counter.shared.count), \(value.description)") Button("+") { Counter.shared.increment() } } BView(value: value) .padding() .border(Color.gray, width: 1) .padding() } } } private struct BView<Value: CustomStringConvertible>: View { let value: Value var body: some View { print("BView.body") return HStack { Text("\(Counter.shared.count), \(value.description)") Button("+") { Counter.shared.increment() } } } } private final class Counter: ObservableObject { @Published private(set) var count: Int = 0 func increment() { count += 1 } static let shared: Counter = .init() } final class Box<Value>: Equatable, CustomStringConvertible where Value: Equatable, Value: CustomStringConvertible { let value: Value init(_ value: Value) { self.value = value } static func == (lhs: Box<Value>, rhs: Box<Value>) -> Bool { lhs.value == rhs.value } var description: String { value.description } } ↑これだと常に更新される。やっぱ == が見られてるっぽい。
7:23 AM
AView(value: Box<Double>(.nan))AView(value: Box<Double>(42)) に変更すると更新されなくなる。
Avatar
これってAViewとBViewをジェネリクスにしてるのは理由があるんですか?
Avatar
とりあえず Equatable な参照型は == が見られるのが確かみたい。
7:24 AM
@hironytic ジェネリックにしてるのは、いろんな型を渡して試しやすいからですね。
Avatar
なるほど。直接 Integer を持つようにしても同じなのかなあとちょっと思っただけです。
Avatar
AView(value: Box<Double>(.nan))AView(value: Double.nan) に変えるだけで参照型を値型にして試せるので。
7:25 AM
Double.nan のときは変更が反映されません。
7:27 AM
Equatable でないクラス Container ↓を作って final class Container<Value: CustomStringConvertible>: CustomStringConvertible { let value: Value init(_ value: Value) { self.value = value } var description: String { value.description } }
7:28 AM
これを渡した場合は、 AView(value: Container<Double>(42)) でも AView(value: Container<Double>(.nan)) でも常に更新が反映されました。
7:28 AM
同一性が見られてるっぽい。
Avatar
omochimetaru 6/25/2020 7:29 AM
いくつか挙動があるなあ・・・
7:30 AM
Doubleの場合: 1でもnanでも同じものを与えると更新されない Box<Double>の場合: インスタンス同一性を見ている EqBox<Double>の場合: Equatable.==を見ている iOS14でこっちで自前のコードで検証したけど同じ (edited)
Avatar
  • 値型→ビット比較
  • Equatable な参照型→ ==
  • Equatable でない参照型→アドレス比較(アドレスのビット比較)
7:30 AM
かなぁ。この挙動から考えると。
7:30 AM
iOS 14 ではどうかわからないけど・・・。
7:31 AM
Equatable な参照型だけ例外的と言えるかも。
Avatar
omochimetaru 6/25/2020 7:35 AM
参照型の場合にEquatableを調べるのであれば、値型のときもそうすればいいだろうに
7:35 AM
逆に値型の場合にビット比較しているなら、参照型の場合もそうすればいいのに・・・
7:36 AM
Avatar
参照型の場合にインスタンスをビット比較するならまだわかるんだけどね。
Avatar
omochimetaru 6/25/2020 7:39 AM
AG::LayoutDescriptor::compare_heap_objects(void const*, void const*, unsigned int, bool) AG::LayoutDescriptor::compare_indirect(AG::ValueLayout&, AG::swift::metadata const*, AG::swift::metadata const*, unsigned int, unsigned char const*, unsigned char const*) AG::LayoutDescriptor::compare_existential_values(AG::swift::existential_type_metadata const*, unsigned char const*, unsigned char const*, unsigned int)
7:40 AM
ジェネリックパラメータ型とExistential型の考慮があるなあ。
7:40 AM
#3 0x00007fff4a9b636e in AG::LayoutDescriptor::compare(unsigned char const*, unsigned char const*, unsigned char const*, unsigned long, unsigned int) () からジャンプしうるシンボル3種類↑
Avatar
Box<Double> をジェネリックでない Number ↓に変えても同じだった。 final class Number: Equatable, CustomStringConvertible { let value: Double init(_ value: Double) { self.value = value } static func == (lhs: Number, rhs: Number) -> Bool { print("Box.==") return lhs.value == rhs.value } var description: String { value.description } }
7:48 AM
ちょっとこれまでの話と関係ないですが、 SwiftUI で @Environment@EnvironmentObject 以外に、コンテクスト的に渡して使えるけど、イミュータブルなものを扱う方法って何かありますか?ダミーのイミュータブルな ObservableObject を作れば良い?
Avatar
@EnvironmentObject って型でマッチされるの?? import SwiftUI struct ContentView: View { var body: some View { SubView() .environmentObject(Foo(2)) .environmentObject(Bar(3)) .environmentObject(Bar(5)) } } struct SubView: View { @EnvironmentObject var foo: Foo @EnvironmentObject var bar1: Bar @EnvironmentObject var bar2: Bar var body: some View { VStack { Text("\(foo.value)") Text("\(bar1.value)") Text("\(bar2.value)") } } } final class Foo: ObservableObject { @Published var value: Int init(_ value: Int) { self.value = value } } final class Bar: ObservableObject { @Published var value: Int init(_ value: Int) { self.value = value } } ↑の実行結果は 2 3 3 (edited)
Avatar
Kishikawa Katsumi 6/25/2020 8:17 AM
それはたぶんそうじゃないかな。期待した結果と違いますか?
Avatar
あ、そういえば、同じ型のものが複数の箇所で指定されてたときは(上の例みたいに同じViewに複数modifierでつけるんじゃなくて、自分から親方向にViewを辿った時に指定が複数あるとき)、一番自分に近い側のものが選ばれるんだろうと勝手に期待していましたが、確認したことなかったです。
8:54 AM
import SwiftUI struct ContentView: View { var body: some View { SubView() .environmentObject(Foo(2)) } } struct SubView: View { @EnvironmentObject var foo: Foo var body: some View { VStack { SubsubView() .environmentObject(Foo(5)) Text("\(foo.value)") } } } struct SubsubView: View { @EnvironmentObject var foo: Foo var body: some View { VStack { Text("\(foo.value)") } } } final class Foo: ObservableObject { @Published var value: Int init(_ value: Int) { self.value = value } }
8:54 AM
結果 5 2 期待通り
Avatar
omochimetaru 6/25/2020 8:55 AM
なるほど。
Avatar
ReactのContext、FlutterでいうところのInheritedWidget(…を使って提供されているProvider)に相当するやつですね。
9:02 AM
しばらくReact、Flutterをやってなかったから名前が出てこなくていま検索しまくってたのはひみつです 🤫
😮 2
Avatar
あ、そういえば、同じ型のものが複数の箇所で指定されてたときは(上の例みたいに同じViewに複数modifierでつけるんじゃなくて、自分から親方向にViewを辿った時に指定が複数あるとき)、一番自分に近い側のものが選ばれるんだろうと勝手に期待していましたが、確認したことなかったです。
二つぶら下げた場合は内側の方が近いと考えれば一貫している?最初に見つかるものを返していると。
9:32 AM
それはたぶんそうじゃないかな。期待した結果と違いますか?
なんか、キー(文字列とか)を指定して引くとかならわかるんですが、型でマッチってちょっと斬新な気がして。同じ型の値を複数付けられないですし。
Avatar
ラップした型を都度作れば良さそう
Avatar
うーん、できるできないというよりも、なんか斬新な設計だなと。
Avatar
omochimetaru 6/25/2020 9:36 AM
文字列はうっかり衝突したりtypoする可能性があるけど、型だったら、そういう心配はないですね
Avatar
DIコンテナってそもそもこんな感じの気がするんですが >型ベースの設計
Avatar
omochimetaru 6/25/2020 9:37 AM
DIコンテナだとわりとよくある気もする
Avatar
うーん、なんというか、( @EnvironmentObject にできるかは一度おいておいて、) NSNumber 二つは紐付けられないけど IntDouble ならできるとか、型でマッチするのって違和感があるというか。
9:58 AM
文字列はうっかり衝突したりtypoする可能性があるけど、型だったら、そういう心配はないですね
安全性に関しては、 @EnvironmentObject は紐付け忘れたらクラッシュだから、そもそも安全なわけじゃないからなぁ。ミスする箇所が減るとは言っても。
Avatar
omochimetaru 6/25/2020 9:58 AM
そういう汎用のデータ型を使うのではなくて、注入する依存性を現した型を専用に定義する感じだと思います
9:59 AM
紐付け忘れはそうですね。 それが気になる場合はObservableObjectをinitで渡す方式の方が良いと思う。 いわゆるコンストラクタDIと言えると思う。
10:01 AM
ただViewがinitした時点で多分チェックされる?から、動的バグとはいっても一度動かせば検出できるたぐいのマシなやつだとも思う (edited)
10:01 AM
ナビゲーションが深くて到達しづらい画面だと見落とすとかはあるかな。
Avatar
その @EnvironmentObject を使う箇所を通過しないとクラッシュしないことないかな?たとえば、ボタン押したクロージャの中から参照してるとか。
10:03 AM
@ObservedObject との比較で言えば、 @EnvironmentObject って @ObservedObject を都度渡してチェーンするのの簡略版だと思うんだけど、 @ObservedObject なら同じ型のものを複数持てるけど、 @EnvironmentObject だとそれができないというところに、「簡略版」以上の不必要な意味を持ってしまっているというか・・・。
Avatar
通過しないとクラッシュしないこと
まああると思います。
10:04 AM
例えばアプリ全体の文字の色を変えるような機能を導入したかったとして
10:04 AM
そのための色のプロパティとか、Style型みたいなものを、全部のビューツリーのinitで伝搬していくのは大変だから
10:05 AM
サボりたいときに使うとかが良いんじゃないか
Avatar
↑の例でいうと、二種類の色を @EnvironmentObject で管理しようとすると破綻しない?
10:05 AM
特別な型を作ればもちろんできるんだけど。
Avatar
Style型を作るイメージですね。そこにいろんな色のプロパティをもたせる。
10:06 AM
class Style: EnvironmentObject { var textColor: Color var fontSize: Int } (edited)
Avatar
いや、当然今の @EnvironmentObject を前提とするとそういう使い方になるんだけど、
10:07 AM
それって API 仕様に引きづられた判断じゃないかな?
10:07 AM
もし identifier 渡す設計になってたらばらばらに紐付けるんじゃない?
Avatar
どうしてもやりたければEnvironmentObjectKeyみたいな仕組みなかったでしたっけ
10:08 AM
去年気になって調べた気がする
Avatar
えっと、やりたい、やりたくないの話じゃなくて、
10:08 AM
@EnvironmentObject が型でマッチする設計になっていることに違和感を感じているという話です。
Avatar
API 仕様に引きづられた判断
実際にStyle型みたいなものは普段から作ってますね
10:09 AM
僕は動的エラーは嫌いなのでコンストラクタDIに寄せることが多いけど
10:10 AM
複数の色情報を管理する事になる場合が多くて、
10:10 AM
そのような場合に伝搬させていく箇所n x パラメータ数m で作業が大変になっちゃうから
10:10 AM
n x 1 にしたいから、型でひとまとめにすることになる
10:11 AM
型じゃないキーにする方法もあったかもしれないというのと、
10:11 AM
絶対に値は与えることになるから、型がキーにできるようになっていると、
10:12 AM
記述量が短くできるからデフォルトの振る舞いとしては便利なんじゃないかしら。
10:12 AM
Identifierの仕組みだと、値とセットで、渡す側と使う側でそれを都度指定しないといけない
Avatar
color scheme 的なものは一つの型にまとめるだろうけど、無関係な二つの色だとばらばらに紐付けたくならないかなぁ。
Avatar
そういうことはありますね
10:15 AM
2つの全く異なるGUI部品ライブラリが混在していて (edited)
10:16 AM
それぞれが別のスタイルセットを要求しているとか。
10:17 AM
同じセットでも、そのStyleを要求してるけど使わないプロパティがあるとかは起きるので
10:17 AM
関心の分離ができてない状態になっちゃいがち
10:18 AM
かつ、そうなると別プロジェクトでの部品単体の再利用性が悪い
10:20 AM
ただ自分は、画面デザインというものが本質的に部品単体であんまり機能しないもので、 ボタンとかテキストとかいろんな要素が横断的に統一感を持つことで成立すると思うから、 どうせ再利用性が低いのでいいかなと思ってる
Avatar
うーん、まあ EnvironmentKey の便利版みたいな位置づけだといいのかなぁ・・・。
Avatar
↑の例でいうと、二種類の色を @EnvironmentObject で管理しようとすると破綻しない?
そういうのは、 @Environment でやることを想定してるんじゃないかなあ。
Avatar
そうですねぇ。 @EnvironmentObject を使う場合は、型でマッチすることを割り切って使う場合って感じでしょうか・・・。
2:22 AM
@Environment だとデフォルト値があるので、付与し忘れてクラッシュがないという違いはありそうですね。
Avatar
えー・・・。昨日の親の変更が子に伝播する件、 ListNavigationLink 使ってるときに、子側をトリガーとして変更が発生したときに、親の List が再描画されると子が pop されて親 View まで戻される現象が発生した・・・。これは許容し難い。どういうケースで起こるのか最小構成作って試してみないと・・・。
9:16 AM
NavigationLink の発行元が消滅したから?
Avatar
omochimetaru 6/26/2020 9:16 AM
元々NavigationLinkを持っていた子が親のListから消えたら巻き戻るのでは?
9:17 AM
昨日の話だと、それでiOS14は一貫してる
Avatar
(実際には消滅してなくて順序が入れ替わってるだけなんだけど)
Avatar
omochimetaru 6/26/2020 9:17 AM
じゃあ子の同一性判定が失敗した? Identifiable になってますか?
Avatar
なってます。
Avatar
omochimetaru 6/26/2020 9:17 AM
それはキツイですね。
Avatar
閲覧履歴を List で表示してて、アイテムを選択してアイテムページに遷移して、そこで閲覧ボタンを押すと履歴が書き換わって pop される・・・。
9:20 AM
NavigationLink の同一性みたいなのがあるのかな・・・。
Avatar
omochimetaru 6/26/2020 9:20 AM
List側が持ってる<C: Collection> のIdentifiableで紐づくと期待してた
Avatar
うん、そうあってほしいよね。
Avatar
omochimetaru 6/26/2020 9:21 AM
ListのRowの子とNavigationLinkの繋がりはよくわかんないけど1番上で包んでるなら紐付いてほしいし。
Avatar
履歴の書き換えは削除と末尾挿入を行ったあとで一回だけ PassthroughSubject 経由で更新通知を発火してるから、入れ替えてる途中に消えてる瞬間もないはずなんだよなぁ。
9:22 AM
とりあえず body が一度しか発火されてないことを確認してみるか。
Avatar
omochimetaru 6/26/2020 9:23 AM
ああ、閲覧履歴だから閲覧したことによって上に来るんですね。
Avatar
はい、そうなんです。やっぱ一度しか発火してないなぁ。
Avatar
omochimetaru 6/26/2020 9:25 AM
iOS14で実行してますか?
Avatar
iOS13です。
Avatar
omochimetaru 6/26/2020 9:29 AM
14なら動くかも
Avatar
とりあえず、 NavigationLink 使わずに UINavigationController 経由で push させれば回避できた。
9:30 AM
こんなこともあろうかと、根っこを NavigationView にせずに UINavigationController にしておいて、カスタム @Environment に渡してある。
Avatar
NavigationLink 鬼門ですね・・・ (edited)
Avatar
僕の使い方が良くないかもしれないし、実プロジェクトは色々絡んでて複雑なので、最小構成で再現するか試したいですね。
9:32 AM
iOS 14 でも試せるように早く beta 落とさないと。
Avatar
NavigationLinkisActive: のBindingをとる版とか、 tag:selection: 版とかだとどれが開いているかっていうStateがこっちが与えたBindingに保持されますが、ないやつはどこが覚えてるんでしょうね。
9:33 AM
「開いているか」というか、どれがpushされているか(isActive に用語を合わせるなら、どれがアクティブか) (edited)
Avatar
omochimetaru 6/26/2020 9:34 AM
NavigationViewがパスとして覚えているのかなと想像してた
Avatar
でも NavigationLink ってそれ自体にはID渡さないですよね。
Avatar
omochimetaru 6/26/2020 9:35 AM
なので構造に応じて勝手に判断しているのかと思ってた
9:36 AM
Listに包まれてたらそのIdentity
Avatar
ということは、それが単にView階層のパスで判断してたら、位置が変わるだけでアウトになりそうなのでそれかも。
Avatar
omochimetaru 6/26/2020 9:37 AM
そうですね、IDとかKeyみたいなものじゃなくて、Viewツリーパスだったら、ダメそうですね
9:37 AM
いや、位置だけだったら、別エントリって事になるのかな
9:38 AM
そこでdiffを見るとか・・・?
Avatar
普通にVStackの中にNavigationLinkを3つ並べたときとかは、VStackの何番目の子かしか判断できませんからね。
Avatar
omochimetaru 6/26/2020 9:38 AM
再描画されてその3つの要素が入れ替わってても、入れ替わったのか中身が変化したのか、判断できないし謎だ
Avatar
????再描画が走っても、順番が入れ替わらなかったら pop されない??
9:42 AM
最新履歴のアイテムを閲覧しても、 body はたしかに走ってるんだけど、 pop されない。
Avatar
Viewツリーのパス説が濃厚になってきました?
Avatar
うーん、でも何で判定してるんでしょう?
9:43 AM
三つ履歴があるときに、最新のを閲覧しても、一番古いのを閲覧しても、結局アイテムは三つのままだから、 (edited)
Avatar
そうか。子の位置だけなら、popされずに別の子になりそうだし。
Avatar
順番が入れ替わったかどうかって Identifiableid でも見ないと判定できなくないですか?
9:44 AM
そこまでしてるなら id 同じ NavigationLink があれば pop しないでほしいけど・・・。
Avatar
ふと思ったんですが、あるViewに @State で持たせた変数って、そのViewが List の中に入ってて、そのリスト上の位置が変わったときって、状態は保持されてるんですかね。
9:49 AM
Viewが @State で状態を持っていて、その親のViewの関係ないところに更新が走ったとしても、 @State で持っている状態は保持されないと困りますよね。でも、親のViewの body は呼ばれるはずだから、子Viewだってイニシャライザが走って別物になっているはず。でも、 @State はどこかで同一性を判定して覚えてくれてるはず。
9:52 AM
これは試してみたくなったけど、これから用事があるので残念。またあとでやってみます。
Avatar
List じゃないケースだと、イニシャライザが走っても @State が保持されてるのは確認したことがあります。
Avatar
import SwiftUI import Foundation import Combine struct ContentView: View { @ObservedObject var history: History = .shared let dateFormatter: DateFormatter = { let value = DateFormatter() value.dateFormat = "yyyy-MM-dd HH:mm:ss" return value }() let items: [Item] = [ Item(id: "A"), Item(id: "B"), Item(id: "C"), ] var body: some View { NavigationView { VStack { VStack { ForEach(items) { item in NavigationLink(destination: ItemView(item: item)) { Text("Open \(item.id)") .padding(4) } } } .padding() Text("履歴") .font(.headline) .padding() Divider() List(history.records.reversed()) { record in NavigationLink(destination: ItemView( item: self.items.first(where: { $0.id == record.id })! )) { VStack(alignment: .leading) { Text("\(record.id)") .font(.headline) Text("\(self.dateFormatter.string(from: record.lastPlayed))") .font(.subheadline) .foregroundColor(.secondary) } } } } } } } struct ItemView: View { let item: Item var body: some View { VStack { Text("\(item.id)") Button("Play") { History.shared.record(for: self.item.id) } } } }
10:33 AM
struct Item: Identifiable { let id: String } final class History: ObservableObject { private(set) var records: [Record] = [] private let subject: PassthroughSubject<Void, Never> = .init() var objectWillChange: AnyPublisher<Void, Never> { subject.eraseToAnyPublisher() } func record(for id: Item.ID) { records.removeAll(where: { $0.id == id }) records.append(Record(id: id, lastPlayed: Date())) subject.send(()) } static let shared: History = .init() struct Record: Identifiable { let id: Item.ID let lastPlayed: Date } }
10:34 AM
↑これで再現しました。
10:34 AM
A, B, C の順に "Open" して "Play" してから履歴から A を選んで "Play" すると pop される。
10:37 AM
[TableView] Warning once only: UITableView was told to layout its visible cells and other contents without being in the view hierarchy (the table view or one of its superviews has not been added to a window). This may cause bugs by forcing views inside the table view to load and perform layout without accurate information (e.g. table view bounds, trait collection, layout margins, safe area insets, etc), and will also cause unnecessary performance overhead due to extra layout passes.
こんな実行時警告出てるけど、そもそも子のアクションで親を再レンダリングしてほしくないなぁ・・・。親に戻ったときに走るならいいけど。
Avatar
Xcode 12 beta 落としたけど同じだな。昨日の実験から考えるとあたりまえだけど。
11:31 AM
[UIContextMenuInteraction] Attempting -[UIContextMenuInteraction dismissMenu], when not in an active state. This is a client error most often caused by calling dismiss more than once during a given lifecycle.
実行時の警告は変わった・・・。
Avatar
struct ContentView: View { var body: some View { NavigationView { FirstView() } .environmentObject(Env(items: [ Item(id: "C"), Item(id: "B"), Item(id: "A"), ])) } } struct FirstView: View { @EnvironmentObject var env: Env var body: some View { List(env.items) { item in NavigationLink(destination: SecondView(id: item.id)) { Text("\(item.id)") } } } } struct SecondView: View { @EnvironmentObject var env: Env let id: String init(id: String) { self.id = id } var body: some View { Button("\(id)") { self.env.items = [ Item(id: "A"), Item(id: "C"), Item(id: "B"), ] } } } struct Item: Identifiable { let id: String } final class Env: ObservableObject { @Published var items: [Item] init(items: [Item]) { self.items = items } } ↑だいぶ単純化しましたが再現しますね。(Aの行をタップして、遷移先でAをタップ)
Avatar
struct ContentView: View { var body: some View { NavigationView { FirstView() } .environmentObject(Env(items: [ Item(id: "C"), Item(id: "B"), Item(id: "A"), ])) } } struct FirstView: View { @EnvironmentObject var env: Env var body: some View { VStack { Button("move") { self.env.items = [ Item(id: "A"), Item(id: "C"), Item(id: "B"), ] } List(env.items) { item in ItemView(id: item.id) } } } } struct ItemView: View { let id: String @State var flag: Bool = false var body: some View { HStack { Text("\(id)") Button(flag ? "true" : "false") { self.flag.toggle() } } } } struct Item: Identifiable { let id: String } final class Env: ObservableObject { @Published var items: [Item] init(items: [Item]) { self.items = items } } ↑ぼくの疑問だった @State は並び替えてもちゃんと保たれている様子。(falseの部分をタップするとtrueとfalseがトグルするので適当に変更してからmoveを押して並び替えてもtrue, falseは保たれて並び変わる)
12:20 PM
import SwiftUI struct ContentView: View { var body: some View { NavigationView { FirstView() } .environmentObject(Env(items: [ Item(id: "C"), Item(id: "B"), Item(id: "A"), ])) } } struct FirstView: View { @EnvironmentObject var env: Env var body: some View { VStack { Button("move") { self.env.items = [ Item(id: "A"), Item(id: "C"), Item(id: "B"), ] } List(env.items) { item in ItemView(id: item.id) } } } } struct ItemView: View { let id: String @State var flag: Bool = false var body: some View { NavigationLink(destination: SecondView(id: id)) { HStack { Text("\(id)") Text(flag ? "true" : "false") .onTapGesture { self.flag.toggle() } } } } } struct SecondView: View { @EnvironmentObject var env: Env let id: String init(id: String) { self.id = id } var body: some View { Button("\(id)") { self.env.items = [ Item(id: "A"), Item(id: "C"), Item(id: "B"), ] } } } struct Item: Identifiable { let id: String }
12:20 PM
↑うん? NavigationLink で遷移して強制的にpopされるパターンだと @State で覚えてるやつもクリアされる。どうも完全にリセットされてるっぽいですね。 (edited)
12:21 PM
(falseの部分をタップするとtrueとfalseがトグルするので適当に変更してから、Aの行をタップして次画面へ遷移したのち、Aをタップしてpopで戻されると、全部falseになってる)
12:24 PM
この例、一度再現したあとに再度同じ操作を続けて行っても、最後に並び替わらないのでpopされないんだけど、そのときBackで戻ってもちゃんと true, false は残ってる。たぶん実際は因果関係が逆で @State で覚えてるようなものがリセットされるからpopされるんだとは思いますが。
Avatar
あれ? FirstViewList のところを、 List { ForEach(env.items) { item in ItemView(id: item.id) } } とすると、いったんpopされるけどすぐにpushされてtrue、falseの状態も保たれてます。 ListForEach の違いはなに? 😓
12:36 PM
この List を消すと(つまり ForEach だけにすると)、TableView表示はされなくなるんですが、AとかBとかの部分を押すと画面遷移はします。ただ、そうすると強制popは起こらなくなります。
12:37 PM
ListNavigationLink の組み合わせでこの現象は起こってるぽいですね。
12:43 PM
https://nalexn.github.io/swiftui-deep-linking/ ↑には ListUITableView と同じで lazily に動作して見えてない部分は作らない機構があって、見えてないとプログラムから遷移させられないという問題が書かれているんですが、今回のもそのlazilyなための仕組みが関係あるかもしれませんね。
Deep Links, Notifications, Quick Actions, Shortcuts and more
Avatar
List が表示されていない間は流れをせき止める ObservableObject でラップすることで、遷移先からの更新の反映を遅らせることができました。 final class ObservableDam<WrappedValue: ObservableObject>: ObservableObject { typealias ObjectWillChangePublisher = AnyPublisher<WrappedValue.ObjectWillChangePublisher.Output, Never> let wrappedValue: WrappedValue private var cancellable: AnyCancellable? = nil var isActive: Bool = true { didSet { if isActive { let outputs = self.outputs self.outputs = [] for output in outputs { subject.send(output) } } } } private var outputs: [ObjectWillChangePublisher.Output] = [] private let subject: PassthroughSubject<ObjectWillChangePublisher.Output, Never> = .init() var objectWillChange: ObjectWillChangePublisher { subject.eraseToAnyPublisher() } init(_ wrappedValue: WrappedValue) { self.wrappedValue = wrappedValue cancellable = wrappedValue.objectWillChange.sink { [weak self] output in guard let self = self else { return } if self.isActive { self.subject.send(output) } else { self.outputs.append(output) } } } }
2:39 PM
↑こういうのを作って、 struct ContentView: View { @ObservedObject var history: ObservableDam<History> = .init(.shared) ↑こうして } .onAppear { self.history.isActive = true } .onDisappear { self.history.isActive = false } ↑こうする。
2:40 PM
戻ってきた瞬間に更新が発火して、アニメーションして最新状態が表示される。 (edited)
Avatar
NavigationLink についての質問を Apple の Forum に投稿したらエラーになって質問全部消えた😂
😢 2
Avatar
とりあえず忘れないうちに書き出してみた。 https://gist.github.com/koher/2b7b6a7b172dbd0585aa754f0ef0a0a7
Avatar
UITableViewの警告が出ていることからも、 List は内部的には UITableView を使って実現されていて、裏で、 UITableViewreloadData() なり beginUpdates()endUpdates() をしてるんじゃないかと推測。
7:34 AM
しかし、画面に見えてない時にそれをするから、UITableView was told to layout its visible cells and other contents without being in the view hierarchyと警告が出て、そして、たぶんレイアウト結果の見えてるセルだけが仮想Viewに作られるところが、見えてないので何も作られなくなって、その結果、 NavigationLink も存在しなくなって戻るのではないかと思いました。
Avatar
↑現象をまとめておきました。 https://qiita.com/hironytic/items/babb07b817e67499174e
Discordの swift-developers-japanサーバー で興味深い話題があったので、気になって調べてみたものを雑にまとめました (話題自体は このへん から) 特に明記していない限り、Xcode 11.5とシミュレ...
🙏 1
Avatar
これiOS 14からの LazyVStack ではどうなるのかがちょっと気にはなってます。前画面でスクロール範囲から見えなくなったらpopされたりすることがあるのだと嫌だなあと。気になってるだけで試してませんが。
Avatar
そして、たぶんレイアウト結果の見えてるセルだけが仮想Viewに作られるところが、見えてないので何も作られなくなって、その結果、 NavigationLink も存在しなくなって戻るのではないかと思いました。
履歴リストの例で、最新の項目を play した場合(つまり、セルの順番は変わらないけど body は走った場合)は pop されないんですよね。これは、仮想ビューツリーの diff を取った結果、変化がないから生き続けてるってことですかね?で、変化があるとレンダリングも必要だから一度破棄されて、表示されてないからレンダリングが行われないから消えてしまう?
9:09 AM
とりあえず NavigationLink 使わずに navigationController を取り出して push したら回避はできるんですが、今度は List のセルに disclosure indicator を出すのが NavigationLink の有無と連動してたり、セル選択のときの UI の挙動(セルの背景がグレーになるとか)が異なってたりでちょっと微妙です・・・。
9:11 AM
今のところ↑に書いた通知を一時的にせき止める ObservableObject をかまそうかと思ってますが、 iOS 14 だと N 階層でも伝わるので、事故を回避するためには List 以下で変更される可能性のあるもの全部に付けないといけなくてかなり面倒です・・・。
9:13 AM
これ、今 static var shared: History だからトリッキーなことに見えますけど、普通に Core Data で @FetchRequest 使ってる場合とか同じじゃないかと思うんですよねぇ・・・。
Avatar
そうですよね。サーバーサイドにCloud Firestoreのようなリアルタイム性があっても普通に起こると思うし、ローカルでもiPadのMultiple Windowsのように片方の画面でデータを変更したのが他方にも伝わるというのもあり得るんで、割と普通に起こることだと思うんですよね 🙁
Avatar
Core Data と CloudKit Database の連携機能とかもありますしねぇ・・・。 https://developer.apple.com/videos/play/wwdc2019/202/ https://developer.apple.com/videos/play/wwdc2020/10650/
CloudKit offers powerful, cloud-syncing technology while Core Data provides extensive data modeling and persistence APIs. Learn about...
Discover how Core Data can help you adopt the CloudKit public database in your app with as little as one line of code. Learn how to...
Avatar
質問させてください。 iOSのGmail Appのような挙動をするリストを実装したいです。 実装方法を教えて頂きたいです。
3:36 AM
このような感じで左右スライドできるものにしたいです。
3:36 AM
gestureを使って左右スライドできる viewをscroll viewを用いてリストを作成することはできました。 (edited)
3:37 AM
しかしそれではスクロールができなくなってしまいました。
3:39 AM
説明下手ですみません。よろしくお願いします。
Avatar
自分で左右スライドだけ実装したものです↓ https://github.com/2357or/DismissibleList/blob/master/DismissibleList/DismissibleCard.swift (edited)
Contribute to 2357or/DismissibleList development by creating an account on GitHub.
Avatar
(自分の能力不足により)まったく質問にお答えすることができなくて申し訳ないのですが、質問と関係ない点で、 https://github.com/2357or/DismissibleList/blob/master/DismissibleList/DismissibleCard.swift#L10 の幅を取得する部分は、スクリーンのサイズじゃなくて GeometryReader を使う方が親のサイズに合わせることができていいのかなあと思いました。たぶん、そうしたからといって質問の件はぜんぜん解決しないとは思いますが 💦
Contribute to 2357or/DismissibleList development by creating an account on GitHub.
👍 1
Avatar
このstackoverflowは同じ内容に見えますね、ここに書いてあるアプローチは全て試しましたか? https://stackoverflow.com/questions/57700396/adding-a-drag-gesture-in-swiftui-to-a-view-inside-a-scrollview-blocks-the-scroll
So I have a ScrollView holding a set of views: ScrollView { ForEach(cities) { city in NavigationLink(destination: ...) { CityRow(city: city) } ...
👍 1
Avatar
@hironytic ありがとうございます。 実際にアプリに組み込む時はGeometryRenderを使おうと思います。 (GeometryRender使うとコードのネストが深くなって実験用には面倒だなと思ったのでスクリーンから取ってました。)
8:34 AM
@tarunon 助かりました!ありがとうございます! stackoverflowにある方法を試してみようと思います。 申し訳ないのですが、自分は高校生でして親にパソコン使用の制限をかけられているので、結果報告が遅くなるかもしれません。すみません。 (edited)
🥳 2
Avatar
できました!!ありがとうございました! タップジェスチャーを追加することでスクロールが可能になりました。 DragGesture()にminimumDistance を追加しても同様にスクロールできました。 (edited)
🎉 7
5:46 AM
Contribute to 2357or/DismissibleList development by creating an account on GitHub.
Avatar
https://discordapp.com/channels/291054398077927425/291211035438874625/730700182039298048 を見ていて、この前の NavigationLink で再描画が波及するのが、 .sheet だとしないかなと思って試してみましたが、 .sheet でも波及しますね。( "Navigation" または "Sheet" ボタンを押してから "+" ボタンを押したときに結果がビューに反映される) struct ContentView: View { @ObservedObject var counter: Counter = .shared @State var isSheetPresented: Bool = false var body: some View { NavigationView { VStack { NavigationLink(destination: CounterView(count: counter.count)) { Text("Navigation") } Button("Sheet") { self.isSheetPresented = true } .sheet(isPresented: $isSheetPresented) { CounterView(count: self.counter.count) } } } } } struct CounterView: View { let count: Int var body: some View { VStack { Text("\(Counter.shared.count)") Button("+") { Counter.shared.count += 1 } } } } final class Counter: ObservableObject { @Published var count: Int = 0 static let shared: Counter = .init() } (edited)
8:53 AM
.sheet では @EnvironmentObject は引き継がれないけど再描画は影響する?無関係の二つかもしれないけど、一貫性がないような・・・。
Avatar
現時点DragGestureを使用する場合、キャンセルを検知すること可能でしょうか? onChanged(_:)onEnded(_:)がありますが、キャンセルされた場合入らないみたいです。
Avatar
updating(_:body:)使っていればキャンセルされた時@GestureStateの初期値になります。クロージャ通らなくて直接に初期値になるので、使い方によって困る場合があるかもしれません。 (edited)
Avatar
たしかにキャンセルが検知されませんね。↓のような hacky なやり方はあるみたいですが・・・。 https://stackoverflow.com/questions/58807357/detect-draggesture-cancelation-in-swiftui
So I have a Rectangle with an added DragGesture and want to track gesture start, change and ending. The issue is when I put another finger on the Rectangle while performing the gesture, the first g...
4:24 AM
Apple のフォーラムで聞いてみるのが早いかもしれません。
Avatar
updating(_:body:)では一応できるところから見ると多分無理かもしれません。 ↑の方法はタップと一緒に使えませんし、タップではなくて0秒のLongPressGestureで時に変になります。 現時点SwiftUIにおいてジェスチャーを避けた方が良さそうですかね。
Avatar
フォーラムはこの前リニューアルされてやる気出してるという話ですし、中の人に伝われば改善につながる可能性もありますし、もしかしたら何か良い方法が出てくるかもしれないですし、ダメ元でフォーラムで聞いてみるのはいかがでしょうか? https://developer.apple.com/forums/tags/swiftui
Connect with fellow developers and Apple experts as you give and receive help on SwiftUI
10:15 AM
↓フォーラムがやる気出してるというソースです。「今なら」が WWDC 期間中を指してるかもしれませんが・・・。 https://discordapp.com/channels/291054398077927425/304939011904897024/725561824497631263
Avatar
なるほど。今までのフォーラムの感じだとあまり期待しないですがね。 聞いてみます。ありがとうございます! (edited)
👍 1
Avatar
↓を実行すると SecondView を開く度に count0 に戻るんですが、どういう挙動でしょう? import SwiftUI struct ContentView: View { var body: some View { NavigationView { NavigationLink(destination: SecondView()) { Text("Open") } } } } struct SecondView: View { @State var count: Int = Counter.shared.count var body: some View { VStack { Text("\(Counter.shared.count)") Text("\(count)") Stepper("", value: $count) .labelsHidden() } .onDisappear { Counter.shared.count = self.count } } } struct Counter { var count: Int = 0 static var shared = Counter() }
5:51 AM
iOS 13 環境です。
5:52 AM
@State が保持し続けられているにしても、 Counter.shared.count で初期化されるにしても、 0 になるのは変だと思うんですが・・・。
Avatar
var count: Int = 0 { didSet { // ここでスタックトレース } } したら何が起きてるかわかりそう
Avatar
そっちは onDisappear のところで更新するだけなんですけど、 @State var count の方に didSet 付けても何も出ないんですよね。 Property Wrapper だから?
Avatar
あーなるほど、これ多分フィールドとして与えると常に使い回されちゃうので
9:10 AM
import SwiftUI struct ContentView: View { var body: some View { NavigationView { NavigationLink(destination: SecondView(Counter.shared.count)) { Text("Open") } } } } struct SecondView: View { @State var count: Int var body: some View { VStack { Text("\(Counter.shared.count)") Text("\(count)") Stepper("", value: $count) .labelsHidden() } .onDisappear { Counter.shared.count = self.count } } } struct Counter { var count: Int = 0 static var shared = Counter() } こうしないといけないかな (edited)
Avatar
若しくはinit()を定義してそこでinit(counter:)を呼ぶとか
Avatar
ダメじゃん
Avatar
多分↑だと NavigationLink を最初に作ったときに一度だけ SecondView.init が走る?
9:31 AM
Counter をクラスかつ ObservableObject にして SecondView では @ObservedObject として持つとかだと簡単なんですけど、そのためだけに Value Semantics 捨ててクラスにするの嫌なんですよねぇ・・・。
Avatar
あーなるほど、そもそもNavigationLinkのdestinationってautoclosureじゃないんですね
9:39 AM
画面遷移してるのにinit一回でコンテキストが永続化されてるのは違和感ある感じするな
Avatar
そうなんですよね。しかも navigation する方法がこれしかないのが辛い・・・( environment とかで UINavigationController 掴ませて、それ経由で UIHostingController に次の View をラップして遷移させるとかはありますが)。
Avatar
onAppear使うのが丸いんじゃないでしょうか
Avatar
そうですねー。今のところそれで回避してるんですが、 0 に戻るのが気持ち悪くて、一体どういうメカニズムなのかなと。
Avatar
多分ですけど、最初のinitで呼ばれた値を覚えていて、Stateをその値にリセットする、みたいなロジックになってそう
10:13 AM
仕様バグの臭いがする
Avatar
何を意図してるのかよくわからないですよね・・・。 destination@autoclosure @escaping じゃないところがそもそも変? (edited)
Avatar
そこがそもそも壊れてると思いますね〜
😂 1
Avatar
私はそれを解決したいときここで提案されている LazyView でラップして解決してますね。 https://medium.com/better-programming/swiftui-navigation-links-and-the-common-pitfalls-faced-505cbfd8029b
Currently, it’s not lazy and that could be dangerous
Avatar
なるほど、 View 自体を lazy にすることで @autoclosure 相当にできるわけですね(本文読めませんでしたが)。
12:03 AM
↓こんな感じかな? onAppear を一つにまとめたいところですが。 struct LazyView<Content: View>: View { private let content: () -> Content @State private var _body: Content? init(_ content: @escaping () -> Content) { self.content = content } @ViewBuilder var body: some View { if _body != nil { _body!.onAppear { self._body = self.content() } } else { EmptyView().onAppear { self._body = self.content() } } } }
12:04 AM
↓この LazyView を挟めばちゃんと動きました。 struct ContentView: View { var body: some View { NavigationView { NavigationLink(destination: LazyView { SecondView() }) { Text("Open") } } } }
12:05 AM
この用途がメインなら、 LazyViewinit@autoclosure も付けた方がいいかな。
Avatar
LazyViewすごいですね、シンプルに問題解決していて凄く良い
🙂 1
Avatar
あれ?なんかもう一度アクセスしたら見れた。↑の記事だと struct LazyView<Content: View>: View { let build: () -> Content init(_ build: @autoclosure @escaping () -> Content) { self.build = build } var body: Content { build() } } になってるけど、 onAppear で更新されてないからこれだと初回しかうまくいかない?
12:48 AM
あれ?そんなことないな。
12:49 AM
なんでだろう。
12:52 AM
initNavigationLink を作るときに呼ばれるけど、 body は View が表示されたときにしか呼ばれず、しかも毎回呼ばれるからか。 (edited)
Avatar
これそもそもViewControllerのオブジェクトが生成されてないのでは。 #beginner-help-archived でも良さそう。
Avatar
すみません。#beginner-help-archivedに投稿し直します (edited)
🙏 1
Avatar
ある Viewbody が無限に呼ばれて固まってしまうという現象が発生して、 @ObservedObject が怪しいからコメントアウトしたら無限コールはなくなったんですけど、原因調査のためにその @ObservedObject を購読してみても何も流れてこずに body だけ呼ばれてるという現象が発生したんですが、何か知見のある方いませんか?最小再現構成はまだ作れてません。 (edited)
Avatar
最小(?)構成できた。 import SwiftUI import Combine struct ContentView: View { @ObservedObject private var foo: Foo = .init() var body: some View { print("body") return Text("Hello, World!") } } final class Foo: ObservableObject { private let subject: CurrentValueSubject<Void, Never> = .init(()) var objectWillChange: AnyPublisher<Void, Never> { subject.eraseToAnyPublisher() } }
2:37 AM
"body" が無限に print される。
2:43 AM
↓だと起こらない・・・。 import SwiftUI import Combine struct ContentView: View { @ObservedObject private var foo: Foo = .init() var body: some View { print("body") return Text("Hello, World!") } } final class Foo: ObservableObject { private let subject: CurrentValueSubject<Void, Never> = .init(()) let objectWillChange: AnyPublisher<Void, Never> init() { objectWillChange = subject.eraseToAnyPublisher() } }
Avatar
objectWillChangeの同一性をチェックして一致しなければ再度subscribeする、みたいな挙動っぽいですね。CurrentValueSubject だからsubscribe毎に値が流れて無限につなげ直しちゃうのかな…?
Avatar
なるほど。たしかに objectWillChange が何度も呼ばれてますね。
6:24 AM
init の際に subscribe されてるイメージだったから、 init が呼ばれずに何度も subscribe されてるという発想がなかった・・・。 objectWillChange を動的に生成するのは良くないのか。
Avatar
直感的じゃないですよねぇ…
😢 1
Avatar
onAppear で create して onDisappear で release したいようなものってどうやって保持するのがいいですか?
2:23 PM
import SwiftUI struct ContentView: View { var body: some View { NavigationView { FirstView() } } } struct FirstView: View { var body: some View { return NavigationLink(destination: SecondView()) { Text("Go") } .onAppear { // ここで create } .onDisappear { // ここで release } } } struct SecondView: View { var body: some View { Text("Second") } }
2:26 PM
FirstView のプロパティに Box 的なクラスのインスタンスを保持しておいて、 onAppear で create されたインスタンスを Box に格納して、その Box を介して onDisappear で release ?
2:26 PM
もし FirstView がコンポーネント的な View で、親 Viewbody が頻繁に再実行されたときにうまく動くか心配。
2:32 PM
struct FirstView: View { var box = Box<Foo>() var body: some View { NavigationLink(destination: SecondView()) { Text("Go") } .onAppear { self.box.value = Foo() } .onDisappear { self.box.value?.release() self.box.value = nil } } } みたいな。 (edited)
2:35 PM
やっぱり↓にしたらちゃんと動かなくなった。 struct ContentView: View { @State var count: Int = 0 var body: some View { NavigationView { VStack { Stepper(count.description, value: $count) .frame(maxWidth: 160) FirstView() } } } } Stepper の値を増減させる度に body が再実行されて FirstViewbox が再生成されるから。
2:40 PM
box の代わりに struct FirstView: View { // var box = Box<Foo>() @State var foo: Foo? として保持させる裏技もあるけど、 @State のようなライフサイクルがほしいだけで、変更検知(して body 再実行)はいらない・・・。
2:41 PM
変更検知なしの @State みたいな Property Wrapper を作ればいいのかな・・・。
Avatar
QRコードを読み取り,特定のワードを検出したら別のViewControllerに移動するということをしたく,様々なサイトを見て作成したのですがアプリ起動時に強制終了してしまいました。ビルド時には特にエラーが出ませんでした。原因の部分を教えください。
Avatar
#beginner-help-archived の方が良いかな。 カメラを使うにはユーザーに許可を求めないといけないのですが、その辺りの設定は出来てますか?
Avatar
そちらのカテゴリに移動させていただきます。 カメラの設定ですがinfo.plistで設定しております
Avatar
@State@ObservedObject のライフサイクルって違うんですね。 @Stateinit が呼ばれても初期化されないけど、 @ObservedObject はされるみたい。
👀 1
5:13 AM
struct ContentView: View { @State var count: Int = 0 var body: some View { VStack { Stepper("\(count)", value: $count) SView() CView() } .padding(30) } } struct S { var value: Int init(value: Int) { self.value = value } } struct SView: View { @State var s: S = .init(value: 42) var body: some View { Stepper("S: \(s.value)", value: $s.value) } } class C: ObservableObject { @Published var value: Int init(value: Int) { self.value = value } } struct CView: View { @ObservedObject var c: C = .init(value: 42) var body: some View { Stepper("C: \(c.value)", value: $c.value) } } 1つ目の Stepper を増減したときに SView はリセットされないけど CView はリセットされる。
5:14 AM
SView では S のイニシャライザは呼ばれてるけど、 @State への書き込みが行われない模様。
Avatar
iOS 14 の @StateObject 使わないといけないのか…。
Avatar
class C: ObservableObject { @Published var value: Int init(value: Int) { self.value = value } } struct C1View: View { @StateObject var c: C = .init(value: 0) var body: some View { Stepper("C1: \(c.value)", value: $c.value) } } struct C2View: View { @ObservedObject var c: C = .init(value: 0) var body: some View { Stepper("C2: \(c.value)", value: $c.value) } } struct C3View: View { @State var c: C = .init(value: 0) var body: some View { InnerView(c: c) } private struct InnerView: View { @ObservedObject var c: C var body: some View { Stepper("C3: \(c.value)", value: $c.value) } } } @State@ObservedObject を組み合わせて @StateObject 相当の挙動を実現できました。↑の C1ViewC3View が同じ挙動します。 (edited)
👀 1
Avatar
iOS 13 & Xcode 11 で動いていた @EnvironmentObject が 1OS 14 & Xcode 12 だと自動的に渡されなくなってしまったんですが、似たような現象が発生した方いませんか?
Avatar
自分のコードでもiOS 14 Xcode 12だとNavigationLinkの遷移で遷移先に渡されなくなったのはありました。 destinationのViewに対して.environmentObject入れてあげたら動いてます
Avatar
ありがとうございます。小さなプロジェクトを作って条件を確かめようとしたら NavigationLink でも自動で渡されて再現条件がわかってないんですが、何かわかりますか?実行時エラーとしてしか検出できないんで、アプリ全体のすべての遷移のパスを確認するには現実的でなく・・・。
Avatar
原因わからないと気持ち悪いけど全部にひたすら .environmentObject 付けまくればいいのか・・・。 (edited)
Avatar
ちゃんとした再現条件など検証はできてないです🙇‍♂️
4:32 PM
ただEnvironmentObjectだけでなく@Environmentでも同じことが起こることがあるというも発見しました
Avatar
こちらでも Environment でも起こっていますね。普通に NavigationLink を書いているだけで起こっている箇所もあり、再現条件がよくわかっていないです。ただ、必ず同じ箇所で起こるので何か条件があるのだと思うのですが・・・。
Avatar
コンテキストを跨がなくなったのでは
4:45 PM
NavigationLinkのdestinationに伝播しない、というのはそういうように見える
Avatar
最小再現コードを作ろうとしたんですが、 NavigationLink で普通に引き継がれて再現できないんですよね。一方実アプリでは特定の NavigationLink で引き継がれない現象が発生していて( iOS 13 では引き継がれた)、何が条件なのかわからず。 (edited)
4:48 PM
実アプリから色々剥がして最小化していけばわかりそうですが、とりあえず .environment, .environmentObject 付ければ解決するので、それで対応してしまいました。原因不明なのは気持ち悪いですが・・・。
4:50 PM
根っこが UIWindow とか、カスタムの TabBarControllerNavigationController が入っていて NavigationView はないとか、色々怪しそうな箇所はあるんですが・・・。
Avatar
swiftUIのlistについて質問があります。 現在listでは、内部でForEachを使用することで、onDeleteを使用することができます。 ですが、deleteボタン以外を追加する方法がわかりません。 色々調べたのですが、左右のスワイプで表示されるボタンの作り方がどうしてもわかりません。 どなたかご教授願えませんでしょうか。 (こういうやつを出したいです) (edited)
Avatar
Delete以外のボタンの追加方法は私はわからないです。(知識不足です) 質問の答えではないのですが、 以前↓ような左右フリック可能なlistを作成しました https://discord.com/channels/291054398077927425/585184753364238346/727398956354502696 自作になりますが、 このような形で、背面にボタンを作成して上のオブジェクトをdrag gestureで動かす、具体的には drag gestureで、オブジェクトの移動量(変化量)が取れるので、それを用いて背面ボタンの形状変化、前面オブジェクトの移動制限をかける とかで作れそうです。 かなり面倒ですけど。。
Avatar
返信遅くなりすみません🙏 背面でやるしかないようですね.... もしくはUIKitを使うしかないのでしょうか.... ありがとうございます!
👍 1
Avatar
WidgetKitやってみた的なスレどこかにありますか?(一応検索したけどなかった)
Avatar
Kishikawa Katsumi 9/26/2020 4:46 AM
見たことないですね。
Avatar
Xcode 11では問題なかったけど、Xcode 12で実行時Issueが出てて、ググっても何もわからなかった問題に遭遇しました 🤔 struct TextInputView: View { @State var inputText: String = "" var body: some View { HStack { TextField("Type here to input text", text: $inputText, //Accessing State's value outside of being installed on a View. This will result in a constant Binding of the initial value and will not update. onCommit: { [self] in self.endEditing(from: self) }) } } } TextFieldtext$inputText 渡すと、実行時にコメントに書いてるとおりのIssueが報告されますけど、内容がよくわかりませんでした 🤔 Stateの値をViewにインストールされる以外のところでアクセスしてるってどういうことだろう?
Avatar
よくわからんけどもしかするとViewInspectorライブラリーと関係あるのかな… 🤔 自分で動作確認するときは問題なくて、UnitTestする時だけたまに出てきます(毎回出るわけでもないので厄介…とりあえず動作時代は問題なく動いてるっぽいので気にしなくて良さそう…かな?
Avatar
UIHostingVCには詰めてますか?
Avatar
詰めてますね、それと関係あるんですか!?
Avatar
詰めていなければStateがインストールされていなくて怒られる、は理解できる挙動なんですけど、詰めていて起こるのか…
3:56 AM
テスト実行時に到達しないif文の中に居たりしませんか?
Avatar
とりあえず引っかかってるのはViewInspectorの確認です
3:58 AM
if文はSceneDelegateのwindowScene探すところ以外使われてないですね… 🤔
Avatar
QuickshaRe is an application that instantly generates and displays a QR code image of data you'd like to share - el-hoshino/QuickshaRe
3:59 AM
これですか?
Avatar
ですです
Avatar
あ、テストコードの方ではUIHostingVCに詰めてないのか
Avatar
あーそういうことか
Avatar
再現するブランチありますか?
Avatar
QuickshaRe is an application that instantly generates and displays a QR code image of data you'd like to share - el-hoshino/QuickshaRe
Avatar
お、ありがたいです
Avatar
こちらこそ!
Avatar
ViewInspectorのドキュメント漁ってみたらどうやら解決法が書いてるらしい 👀 https://github.com/nalexn/ViewInspector/blob/master/guide.md#views-using-state-environment-or-environmentobject
Runtime inspection and unit testing of SwiftUI views - nalexn/ViewInspector
Avatar
あーなるほどなぁ
4:11 AM
確かにそれなら確実にインストールされたあとに実行出来ますね
Avatar
Approach 1だと本体のコードに修正入れないといけないのが微妙に嫌だから、Approach 2やってみるかな 🤔 (edited)
Avatar
UIHostingControllerに詰める作戦はダメっぽかったです
Avatar
残念 😢
4:13 AM
とりあえずありがとうございます!!
Avatar
でもテストのために実際のViewに手を入れないといけないのはスマートじゃないですね…
Avatar
ですね、テストのために本体に手を入れたくない
Avatar
この辺はAppleが良いSwiftUI向けのテストフレームワークを提供するか、ViewはユニットテストしないでUIテストで担保しなさいという声明が欲しい
Avatar
でもせっかくSwiftUIのViewがstruct化してVCの面倒臭いライフサイクルから解放されたんだから、ユニットテストしたいですよね 🤔
Avatar
(結局Stateのライフサイクルに踊らされている…)
Avatar
暗黙のライフサイクルが増えた分余計にたちが悪いと思ってます。
Avatar
:wakaru:
Avatar
↓のような階層のときに、初回表示時のみ FooViewControllerviewDidAppear が呼ばれない現象が発生しているんですが、何かわかる方いませんか? 1. ContentView: View 2. UIHostingController<PageView> 3. PageView: UIViewControllerRepresentable 4. PageViewController: UIPageViewController 5. UIHostingController<FooView> 6. FooView: UIViewControllerRepresentable 7. FooViewController: UIViewController 5, 6 を抜くと viewDidAppear が呼ばれます。 UIHostingController が悪いのかと思ったんですが、 2, 3, 4 を抜いた状態でも viewDidAppear が呼ばれます。再現コードは↓です。 https://github.com/koher/page-view-did-appear-experiment
Avatar
Kishikawa Katsumi 10/27/2020 5:53 AM
override var shouldAutomaticallyForwardAppearanceMethods: Bool { return true } これをUIPageViewController(のサブクラス)に足してみてください。 (edited)
Avatar
@Kishikawa Katsumi すみません、今気づきました。↑で viewDidAppear が呼ばれるようになりました!!ありがとうございます。
😄 1
Avatar
Kishikawa Katsumi 10/27/2020 9:26 AM
よかったです。これは理由があってこうなんですかねえ。
🙇‍♂️ 1
Avatar
4 の問題だとすると 5, 6 を省いたときは呼ばれてたのがよくわからないですが・・・。
9:28 AM
5 に shouldAutomaticallyForwardAppearanceMethodsoverride したらうまくいった方がしっくりきますね・・・。
9:28 AM
でも、それにしても 2, 3, 4 を省いたときは 5, 6 があっても呼ばれる意味はわからないか。
Avatar
5 は元々 shouldAutomaticallyForwardAppearanceMethodstrue のようでした。 4 を true にしても 5 を false にすると表示されなくなりました。
9:42 AM
また、 4 で shouldAutomaticallyForwardAppearanceMethodsfalse にしても、 UIHostingController を介さずに( 5, 6 を省略して)直接 7 を表示した場合は呼ばれました。
Avatar
Kishikawa Katsumi 10/27/2020 9:42 AM
けっこう普通の構成のUIPageViewControllerでもshouldAutomaticallyForwardAppearanceMethodsのオーバーライド必要じゃないっすか?
9:42 AM
なるほど?
Avatar
UIPageViewController あまりこれまで使ったことがなく💦
Avatar
Kishikawa Katsumi 10/27/2020 9:43 AM
原因はそこで使うビューコントローラのサブクラスによってデフォルトがいろいろ違うのかな?
9:44 AM
私もいうほど使わないんですけど、手元のプロジェクトでどうだったかなと確認した感じだと、各ページのビューコントローラのviewDidAppearを呼ぶにはオーバーライド必要っぽいです。
Avatar
1, 5, 6, 7 でも、 1, 2, 3, 4, 7 でも呼ばれてたのが不思議なんですよね。
9:45 AM
各ページのビューコントローラのviewDidAppearを呼ぶにはオーバーライド必要っぽいです。
なるほどー。勉強になりました。
Avatar
Kishikawa Katsumi 10/27/2020 9:46 AM
1, 2, 3, 4, 7 で同じ挙動にならない(呼ばれないはず)のが不思議ですね。
Avatar
そうなんですよ。
Avatar
The #if/#endif compiler conditionals surrounding PreviewProvider types have been removed from SwiftUI templates. PreviewProviders aren’t properly removed from built products when archived. (51539802) https://developer.apple.com/documentation/xcode-release-notes/xcode-11-release-notes#3384944
PreviewProviderに#ifが必要ないのって、あくまでDead Code Elimination最適化で消えるからであって言語の機能じゃないですよね? 僕はこれが気持ち悪くていつも#ifでリリース時に入らないようにしているんですが、皆さんどうしてますか?
(edited)
Avatar
テンプレートから消えたなら僕はそっちに合わせる
5:29 AM
無いほうがスッキリするし、最悪出荷されても大丈夫だし。
Avatar
最悪出荷されても大丈夫
うむーまあ大抵の場合はそうか〜
Avatar
最悪大丈夫な場合には最適化を信じるってのもわりと普段もそうだし。
Avatar
最適化を信じる…
Avatar
すごいDCEの使い方だ・・・
Avatar
そもそも通常のPreviewの時点で、internalな型であるPreviewProviderがDCEで消されていないっていう過程の上で動いてるからなぁ (edited)
Avatar
あれ、それはたしかにそうだな?
5:46 AM
ビルド前にコード書き換えでpublicにされてるとかならわかる
Avatar
ありそう
5:47 AM
-enable-private-imports でビルドされてるんだっけな
Avatar
Foo から $foo.bar でプロパティごとに分解して Binding<Bar> が得られるのの逆がやりたくなることありませんか?複数のバラバラの @State などから結合して一つの Foo として子 View@Binding に渡したいなど。
Avatar
SwiftUIはやってないけど、ストリームプログラミングで普通にやりたいことはよくあります (edited)
Avatar
↑の場合、 struct Foobar プロパティの実体だけ @State var bar: Bar にするとかできないのでどうしたもんかなと。 @Binding var foo: Foo が変更検知して @State var bar: Bar に書き戻すというのもうまく書けず・・・。
Avatar
なんかやり方ありそうな気がしますけどね。識者〜
Avatar
Binding のイニシャライザの get, set で中継できないかと思ったんだけど、その @Binding なプロパティを初期化するときに self をキャプチャできないし、イニシャライザから同期的に get が呼ばれるようで get の中から呼び出す実体を初期化直後に IUO で差し替えようとしたらその前に nil の Forced Unwrapping で死んだ・・・。
Avatar
Bindingのsetの返り値がVoidなのが厳しいのかなあ。
Avatar
Twitter のタイムラインを↓みたいに表示するとして、 List { ForEach(timeline.tweets) { tweet in TweetView(tweet) } } TweetView からいいねが押されたかを tweet.isLiked = true みたいにして変更できるとすると tweetBinding で渡さないといけませんが、 ForEach を噛ましてるのでできなくてこういうパターン辛くないですか?
Avatar
こういうのは末端で変更できないからハンドラ経由で親側でStateを弄るか、 或いはReduxっぽい仕組みを導入することになりそう
Avatar
Binding<Value: RandomAccessCollection>Element == Binding<Value.Element>RandomAccessCollection にすることで、 ForEach を介して要素を Binding として取り出して子 View に渡せないかな?
11:14 AM
List { ForEach($timeline.tweets) { tweet in TweetView(tweet) } } (edited)
Avatar
Avatar
koher
Binding<Value: RandomAccessCollection>Element == Binding<Value.Element>RandomAccessCollection にすることで、 ForEach を介して要素を Binding として取り出して子 View に渡せないかな?
こんな感じでできたけどどうでしょう? https://gist.github.com/koher/095eed09a3f8369bd0e72dbd9e689a72
Avatar
List { ForEach(0..<timeline.tweets.count) { i in TweetView($timeline.tweets[i]) } }List { ForEach(0..<timeline.tweets.count) { i in TweetView( .init( get: { timeline.tweets[i] }, set: { timeline.tweets[i] = $0 } ) ) } } みたいな形をよく使いますが、前者は onDeletetimeline.tweets の要素を削除したときなど条件によっては Index out of range を引き起こしたり、後者は NavigationLink を使った画面遷移を壊したり (親ビュー側が再描画されることにより) するので微妙だったんですよね。koher さんのは良さそうな? (edited)
Avatar
あと、少し前に刷新された Apple のチュートリアルのうちのこれで、koher さんのと同じようなことを extension を使わずにやってましたね。 https://developer.apple.com/tutorials/app-dev-training/passing-data-with-bindings
Avatar
@kebo 情報ありがとうございます!↓の部分ですね。 struct ScrumsView: View { @Binding var scrums: [DailyScrum] var body: some View { List { ForEach(scrums) { scrum in NavigationLink(destination: DetailView(scrum: binding(for: scrum))) { CardView(scrum: scrum) } .listRowBackground(scrum.color) } } .navigationTitle("Daily Scrums") .navigationBarItems(trailing: Button(action: {}) { Image(systemName: "plus") }) } private func binding(for scrum: DailyScrum) -> Binding<DailyScrum> { guard let scrumIndex = scrums.firstIndex(where: { $0.id == scrum.id }) else { fatalError("Can't find scrum in array") } return $scrums[scrumIndex] } }
👍 1
9:13 AM
ForEach の O(N) と binding(for:) の O(N) の組み合わせで O(N^2) になるのが辛そうですね。 N が大きい場合。順序を保持して ID から O(1) で子を引けるコンテナがほしいですね。標準であればベストですが、 ArrayDictionary を組み合わせて作るのが良さそう。
Avatar
UIViewControllerRepresentableframe を指定しなかったときのサイズがどうやって決まるか知ってる方いませんか? constraints を見る限り、 SwiftUI によって View Controller の viewconstraint が設定されているようなんですが、 View Controller 側に preferredContentSize を指定しても負けてしまうことがあり、また、 AutoLayout の heightAnchor 等で設定しても SwiftUI が設定した constraint と conflict してしまい・・・。
2:36 PM
今のところ唯一うまくいったのが↓こんな感じのハックで AutoLayout が計算したサイズを Binding で戻して frame に食わせるという方法なんですが、もう少しいい方法がある気がしています(この例だと preferredContentSize でうまくいくんですが、もう少し複雑な例ではうまくいきませんでした)。 https://gist.github.com/koher/13506ea9c45ebfd8b04249a07b23828f
Avatar
13~14の全部でScrollView 関連のサイズ決定がバグってる気がします。Binding で戻す方法はList の中身をForEach で回す時に厳しくて、いい方法があるなら知りたいです。(今のところpreferredContentSize で動いてるので、うまくいかない例が知りたいです)
Avatar
実プロジェクトのかなり複雑なレイアウトでうまくいかないんですが、単純なプロジェクトで再現できたら共有します。
Avatar
↓に報告されている、キーボードを開くと onAppear が呼ばれるバグ(?)が再現してるんですが(僕の場合は TabView ではなく NavigationView のルートの onAppear が呼ばれるというパターンですが)、これ結構きつくないですか? onAppear でページ表示されたことをトリガーとして何かやりたいパターンとか。 https://stackoverflow.com/questions/64027482/onappear-calls-unexpectedly-when-keyboard-appears-in-swiftui
I am experiencing very odd behavior in SwiftUI 2.0 and iOS14. When the keyboard appears on the screen, the OnAppear method of other tab's view called automatically. However, this works fine Xcode 1...
Avatar
import SwiftUI extension View { func onAppear2(_ perform: @escaping () -> Void) -> some View { OnAppearView(content: self, perform: perform) } } private struct OnAppearView<Content>: UIViewControllerRepresentable where Content: View { let content: Content let perform: () -> Void func makeUIViewController(context: Context) -> OnAppearViewController<Content> { OnAppearViewController(content: content, perform: perform) } func updateUIViewController(_ viewController: OnAppearViewController<Content>, context: Context) { viewController.rootView = content } } private final class OnAppearViewController<Content>: UIHostingController<Content> where Content: View { let perform: () -> Void init(content: Content, perform: @escaping () -> Void) { self.perform = perform super.init(rootView: content) view.backgroundColor = .clear } @objc required dynamic init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) perform() } } ワークアラウンド思い付きました。 onAppear の代わりに↑の onAppear2 を使えば回避できました。
Avatar
onAppearはヤバくて、私は一切使わずに状態管理する、になってます。 TabかNavigationViewで壊れるのでほぼ確実に踏むという。
Avatar
ページが表示されたときにAPI叩いてデータ取ってきたいとかはどうしてますか? (edited)
Avatar
タブ切り替えをEnvironmentObjectから拾ってきてやってますが、iOS13.2と13.3でxcuitestがクラッシュします。
😂 1
9:17 AM
実機だと起きないはずなんですが、実機がなくて確認できない😇
Avatar
PageTabViewStyle を使ったときって縦スクロール強制なんでしょうか。それだと使えなさすぎるんですが・・・。ググっても↓みたいなむちゃくちゃな方法しか出てこず。 https://stackoverflow.com/questions/64091415/how-to-disable-vertical-scroll-in-tabview-with-swiftui UIViewControllerRepresentableUIPageViewController 使えばいいんですが、面倒だなと・・・。
I have set up a TabView in my application, so that I can swipe horizontally between multiple pages, but I also have an unwanted vertical scroll that may appear, with a bounce effect so. How can I d...
Avatar
あれ?SwiftUIいつからこの出し分けができるようになったっけ? struct ContentView: View { var body: some View { if Bool.random() { Text("abc") } else { Color.red } } } (edited)
Avatar
yutailang0119 3/18/2021 1:47 PM
ifはだいぶ前から使えたと思うけど
Avatar
if は使えるけど、昔は中身同じ型じゃないとダメだったはず https://yanamura.hatenablog.com/entry/2019/09/05/150849
potatotips #64でLTした内容です。 SwiftUIのViewで条件によってViewをだしわけたいことがたまにあるかと思います。 SwiftUIでこのように条件によってViewを出し分けるようなコードを書いてみます。 var body: some View { if imageName.isEmpty { return Text("no image") } else { return Image(imageName) } } そうするとこのようなコンパイルエラーになってしまいます。 ! Function declares an opaque return type, but the…
Avatar
yutailang0119 3/18/2021 1:49 PM
if letで分けるの使ってたけど、いつって言われると覚えてないな
Avatar
body@ViewBuilder になってますね。いつからでしょう。 (edited)
Avatar
ありがとうございます!
4:01 PM
ところで、iOS14以降の純粋SwiftUI(UIApplicationDelegateとかUISceneDelegateがない方のSwiftUI)で、UIInterfaceOrientation を取得する方法ありますか?( UIDeviceOrientation ではない方)
4:04 PM
使い道としては、AVCaptureVideoPreviewLayer を使わないで純粋にPixelBufferをCGImageに直してImageを作る方法でカメラアプリを作ってますが、そのためにはデバイスの向きよりもUIの向きを取得しないといけないので 👀
Avatar
UIApplication.shared.windows.first?.windowScene?.interfaceOrientation 無理矢理感がありますが、これダメですか?
Avatar
カメラからの CVImageBuffer を直接使う系のアプリで UIDevice.current.orientation から switchUIInterfaceOrientation を生成する方法なら使ったことがありますね。 (edited)
Avatar
デバイスの物理的な向きは加速度使わないと取れないです
11:10 PM
UIの向きはユーザーが任意で固定できるので。
11:12 PM
で、UIの向きは固定されててもいいのであれば UIDevice から取れますね。
Avatar
コメントありがとうございます;ライブラリーにして配布したいので、実際ユーザがUIInterface固定するかしないかの制御が自分の方ではできないので、やはりUIDeviceから取得するものはちょっと使いづらいですね…(もちろん固定するのが一番楽だし変な回転動作もないのはわかりますが) とりあえず無理矢理windowSceneから取ることにしましたけど、やはりSwiftUIもうちょっとちゃんとしたプロパティー作ってほしい 😇
👍 1
Avatar
UIApplication はextensionからは使えないから、そこんとこ注意
Avatar
Avatar
niw
UIApplication はextensionからは使えないから、そこんとこ注意
え? extension Hoge から UIApplication 使えないってことです?
Avatar
Avatar
lovee
え? extension Hoge から UIApplication 使えないってことです?
あ、違う。その extension じゃないw AppExtension。ライブラリとして公開するなら UIApplication への依存があると AppExtension からは使えなくなる。
Avatar
ああ、AppExtensionですね 😅 一瞬びっくりしましたw
Avatar
なるほど、まあでもどうせAppExtensionはそもそもカメラ使えない…はず?
Avatar
そうなのかな? そっちの制約は覚えてない...
Avatar
https://developer.apple.com/jp/app-extensions/ これ見る限り使えなさそうな予感ですね 🤔
App Extensionを使用すると、Appの枠を超えてカスタム機能やコンテンツを拡張でき、ユーザーが他のAppやシステムを操作している間でも、その機能やコンテンツを利用できるようになります。
Avatar
App-Extension-Safe フラグみたいなのつけてビルドするとわかるんだっけか。
8:02 PM
iMessageのextensionとか、カメラつかえてもいいのになあとは思うけど。 (edited)
Avatar
例のライブラリーリリースしました;結局 CGImage 使って Image でプレビュー出すと遅延が結構出る(0.5秒くらい?)ので、諦めて素直に AVCaptureVideoPreviewLayer 使うことにしました 😇 https://github.com/el-hoshino/MetadataScanner
A MetadataScanner view for SwiftUI. Contribute to el-hoshino/MetadataScanner development by creating an account on GitHub.
👏🏼 2
Avatar
UIViewControllerRepresentableframe問題、iOS14だとmakeUIViewControllerの中でintrinsicContentSizeでいいサイズを計算してpreferredContentSizeに設定してやったVCをreturnするとうまくSwiftUI側に認識されそうですね。ScrollViewのスクロール方向のConstraintをsystemLayoutSizeFittingで作ったらうまくいきました。iOS13はダメです。
👀 1
Avatar
よくわからないけど、List の中においてる Button ですが、表示は正しくできてるのに、押した時の動作が違うボタンのものになるし、ハイライトもその間違ったボタンが反応しますけどなんで? 😇
7:32 PM
7:34 PM
実行イメージ
7:36 PM
実機で動作確認すると一応まともに動いてはいるっぽい… 🤔
7:38 PM
まさかと思ってシミュレータ再起動したら治ったっぽい 😇 俺の2時間の睡眠時間を返せ
😅 1
Avatar
Kishikawa Katsumi 4/4/2021 7:47 PM
そんなことがあるのか〜
Avatar
.tagがついてないから?
Avatar
.tag は関係ないはずです、あっても外しても動作変わらなかったのと、あれそもそも ScrollViewReaderscrollTo メソッド用のものなので
Avatar
今は仕様かわったんでしょうかね?iOS13初期のころは.tagついてないと上のようにループでボタン並べたときに正しく識別されないみたいなことはありました
4:36 AM
ボタンではなくセルを並べたときに正しく更新対象が検知されないという話だったかも
Avatar
あーそれなら仕様変わったかもしれません、私の方はDeployment TargetをiOS 14にしていますので、iOS 13だとまた動作が変わるかもしれません 🤔
Avatar
この間CatalystのSwiftUIでonDropが動作しなかったんですけど動いている方いますか…? macos向けのプロジェクトとして作るとonDrop動くんですけど、Catalyst(Optimize for macも)だと動かない気がするんですよね、、
Avatar
withAnimation って使いづらくないですか?たとえば、 ObservableObject に適合した ViewModel が非同期的に状態を変更するようなときに withAnimation を適用しようとすると、 ViewModel 側に withAnimation 書かないといけなくないですか? ViewModel に SwiftUI を漏れ出させたくないですが・・・。
7:41 AM
// これは問題ない if let isFoo = viewModel.isFoo { Text("Hello") } Button("Toggle Foo") { withAnimation { viewModel.isFoo.toggle() } } // これはどうする? if let imageData = viewModel.imageData { Image(UIImage(data: imageData)!) } Button("Load Image") { viewModel.loadImage() // 非同期なのでここでは withAnimation で囲めない } (edited)
Avatar
Avatar
koher
// これは問題ない if let isFoo = viewModel.isFoo { Text("Hello") } Button("Toggle Foo") { withAnimation { viewModel.isFoo.toggle() } } // これはどうする? if let imageData = viewModel.imageData { Image(UIImage(data: imageData)!) } Button("Load Image") { viewModel.loadImage() // 非同期なのでここでは withAnimation で囲めない } (edited)
MVVM と withAnimation の相性が悪いのは同意です。 ただ、その例の場合って if let imageData = viewModel.imageData { Image(UIImage(data: imageData)!) .animation(.easeInOut) } とかでいけません? (あるいはカスタムの AnyTransition を作っておくなど) (edited)
Avatar
Avatar
kebo
MVVM と withAnimation の相性が悪いのは同意です。 ただ、その例の場合って if let imageData = viewModel.imageData { Image(UIImage(data: imageData)!) .animation(.easeInOut) } とかでいけません? (あるいはカスタムの AnyTransition を作っておくなど) (edited)
手元で試してる限り、 if で分岐して現れるのはアニメーション利かないんですよね。 .opacity(isFoo ? 1.0 : 0.0).animation(.easeInOut) とかなら利くんですが。
Avatar
@ViewBuilder 内で直接使っているとかだと動かないんですが、例えば @ViewBuilder var mainBody: some View { if someCondition { ... // ここで .animation(.easeInOut) はアニメーション効かず (分岐先が変わると View そのものが置き換わるため) } else { ... // ここで .animation(.easeInOut) はアニメーション効かず (分岐先が変わると View そのものが置き換わるため) } } var body: some View { VStack { self.mainBody } .animation(.easeInOut) // これは効く } とかだと効くんですよね。
Avatar
@ViewBuilder をワンショットで使えるクロージャを作っておくと便利そう (edited)
10:07 AM
func make<V: View>(_ f: @ViewBuilder () -> V) -> V { f() } ... var body: some View { make { if { ... } else { ... } }.animation(.easeInOut) } これで動くんじゃないかしら (edited)
👀 1
Avatar
それって、 Group { if isFoo { Text("Foo") } } .animation(.easeInOut) と違いはありますか?
Avatar
Groupでいいですね確かに
10:09 AM
Groupなのか?というのはおいといて、転用できる
Avatar
でも Group でも利かないんですよね・・・。
Avatar
ええ〜
10:10 AM
じゃあKeboさんの例だとVStackがあるから効いてるってことですね
10:11 AM
実体を持つViewにしないとだめってことじゃないかな
10:11 AM
仮想DOMにしか存在しない概念にはアニメーションがつかない
👀 2
Avatar
↓今書いてるコードそのもの Group { if isFoo { Text("☔") .font(.system(.largeTitle)) } } .animation(.easeInOut(duration: 4.0))
Avatar
ためしにGroupをVStackにしてみて
Avatar
仮想DOMにしか存在しない概念にはアニメーションがつかない
なるほど。
Avatar
Avatar
tarunon
ためしにGroupをVStackにしてみて
おお、利いた!なるほどー、そういうことなのか。
Avatar
やったぜ
Avatar
ありがとうございます!
10:12 AM
確かにアニメーション以外でも実体のない View でなんか思い通りに動かないの踏んだことありますね。
Avatar
むずかしいなあ
Avatar
.animation 付けたら実体化してほしい。
Avatar
実体化するとサイズ計算に差分生まれるからキチィ
Avatar
そもそも私がなぜ気づいたかというと、 @ViewBuilder var mainBody: some View { if someCondition { ... } else { ... } } var body: some View { self.mainBody .onAppear(...) .onDisappear(...) }someCondition の状態が変わるたびに onDisappearonAppear が呼ばれるところからだったんですよね。多分 Group だとそれと同じことが起きてる気がします。
Avatar
Avatar
tarunon
実体化するとサイズ計算に差分生まれるからキチィ
https://discord.com/channels/291054398077927425/585184753364238346/847054279448723496 みたいなケースで .animation が返す some View が実体化されるものになるだけでもダメですか?
Avatar
ifの中と外が複雑になった場合に (edited)
10:17 AM
間に実Viewを挟むかどうかでサイズ計算が変わる可能性は大いに考えられるのでだめじゃないかと思います (edited)
Avatar
Avatar
kebo
そもそも私がなぜ気づいたかというと、 @ViewBuilder var mainBody: some View { if someCondition { ... } else { ... } } var body: some View { self.mainBody .onAppear(...) .onDisappear(...) }someCondition の状態が変わるたびに onDisappearonAppear が呼ばれるところからだったんですよね。多分 Group だとそれと同じことが起きてる気がします。
これきついですね。 onAppear, onDisappear が分岐先の実体まで展開されちゃうのか・・・。
Avatar
Avatar
tarunon
間に実Viewを挟むかどうかでサイズ計算が変わる可能性は大いに考えられるのでだめじゃないかと思います (edited)
ここでいう「サイズ」とは、 View の実体の表示上のサイズ( CGSize )のことですか?レイアウトへの影響の話?
Avatar
両方です
Avatar
あ、両者は同じ意味の意図でした。もっと別のサイズ(仮想 View の変更検知のためのデータとか)の意味もあり得るのかなと思って。
10:21 AM
なるほどー、たしかにレイアウト上の変な影響は出そうな気もしますね・・・。
Avatar
まあ真面目にこれ対策するとしたら
10:21 AM
実DOMの存在を示す型を作っちゃって(protocol RealViewとか)
10:21 AM
それにしかanimationを許可しないってやるといいんじゃないかな
10:21 AM
Apple頑張ってくれ
Avatar
そういえばもうすぐWWDCだし
Avatar
うーん、 React と違って仮想 View が消える点は SwiftUI のアピールポイントのように喧伝されてたけど、結構闇を生んでそうですね・・・。
Avatar
大型アプデ来るのかな。
Avatar
行き過ぎた最適化が消滅を生むの、僕らは何度もコンパイラで学んだはずなんだよね
Avatar
RealView の標準提供ほしいですね。
10:23 AM
標準でそういう使い方をするものなんだと言われたら意識しやすいし納得もできそう。 AnySequence みたいな。
Avatar
特定のprotocolつけたときに@available(*, unavailable) つけて封印とかってできましたっけ (edited)
Avatar
AnyView で良かったりする?
Avatar
当然なんですけどAnyViewで消失したタイミングで色々崩壊するので(AnyViewは実Viewではない)
10:24 AM
あれ、あってたっけ。。。
10:24 AM
AnyViewの有無でレンダリングが変わるってのを、 @Iceman が報告してた記憶がある
Avatar
AnyView じゃダメでした。
Avatar
AnyView、完全に仮想Viewのくせに存在するだけでレンダリングに悪さをする可能性 (edited)
Avatar
AnyView(Group { // ViewBuilder 利用するために Group をかます if isFoo { Text("☔") .font(.system(.largeTitle)) } }) .animation(.easeInOut(duration: 4.0)) (edited)
Avatar
Avatar
tarunon
AnyView、完全に仮想Viewのくせに存在するだけでレンダリングに悪さをする可能性 (edited)
ひどい😂
Avatar
そのときいたのに何も覚えてなかった😅
Avatar
私のこの機能もそのうち加齢と共に失われていくと思うので
10:31 AM
誰かが引き継いでくれるとうれしいですねw
Avatar
今日 case .foo(let bar)case let .foo(bar) かの話をしてて、 https://discord.com/channels/291054398077927425/291054454793306112/339971613309141024 は引き出せたんですけどねー。 Discord 何年も前の会話が残ってるから、自分がいかに覚えてないか気付かされますね💧
Avatar
これはlet後のがアドって話だったはず
Avatar
はい、これ以来内側 let に改宗してそれで覚えてました。
Avatar
AnyViewにラップするだけでレイアウト壊れるやつ、最小実装作れてないな〜
11:08 AM
今度作ってみよう
👀 1
😩 1
Avatar
struct ContentView: View { var body: some View { VirtualScreen(resolution: CGSize(width: 1280, height: 960)) { // AnyView(Color.white) Color.white } .background(Color.gray) } } struct VirtualScreen<Content: View>: View { var resolution: CGSize var content: Content init(resolution: CGSize, @ViewBuilder content: () -> Content) { self.resolution = resolution self.content = content() } var body: some View { GeometryReader { context in // GeometryReader { _ in self.content // } .frame(width: self.resolution.width, height: self.resolution.height) .scaleEffect(min(context.size.width / self.resolution.width, context.size.height / self.resolution.height)) } } }
12:43 PM
🥺 2
😇 2
12:43 PM
AnyViewにラップするとずれた感じになります。確認したところiOS14では再現しなくなりました
Avatar
やっぱiOS13はpublic betaやね
Avatar
またコメントアウトしているGeometryReaderのところ有効にすると同じ表示になります
Avatar
Kishikawa Katsumi 6/9/2021 2:15 AM
SwiftUI has a new, pretty cool, debugging utility to help you understand what is causing a view to be reevaluated. Call Self._printChanges() inside the body of a view to print out the changes that have triggered the view update.
Retweets
216
Likes
1107
Avatar
iOS 15 でいろいろ便利になったらしいので雑にまとめてます https://koogawa.hateblo.jp/entry/2021/06/10/131424
ツイートをまとめただけです。 developer.apple.com #WWDC21 さて、What's new in SwiftUI のセッションもみていくわよ https://t.co/FZEk3wOxJm— Kosuke Ogawa☁Aluエンジニア🏝宮崎 (@koogawa) 2021年6月10日 #wwdc21 まったく新しい Apple Pay のフローや、天気アプリは SwiftUI によってリデザインされたんですって pic.twitter.com/QVLh2Okr9G— Kosuke Ogawa☁Aluエンジニア🏝宮崎 (@koogawa) 2021年6月10日 #WWDC2…
👀 4
💯 1
Avatar
TextにMarkdownサポートあるとリアルタイムプレビューできるエディタとかかんたんに作れそうですね。ちょっと面白そう https://twitter.com/koogawa/status/1402837979383558144
#WWDC21 SwiftUI でテキストの一部をリンクにするのとかめっちゃ捗るやん🎉
👍 1
Avatar
Miwa / Ensan 6/10/2021 5:49 AM
SubmitLabelが地味に嬉しいんですが、UIReturnKeyTypeにあった.yahooと.googleと.emergencyCallが消えてしまったのはなんでだろう🤔
Avatar
yutailang0119 6/10/2021 6:11 AM
@trickart @koogawa Digital LoungeでAppleのエンジニアが言ってたけど、ユーザーのinputをMarkdown解析することは、ユースケースとしては考えていないっぽい感じでした
😑 2
Avatar
多分だけど、i18nの改善が大元にあって、attributed stringのリテラル表現が必要になって
6:46 AM
markdown的なリテラルを導入した、って感じじゃないかなあ。
6:47 AM
markdown的なリテラルの導入はi18nの改善は大きな理由の一つだったと思う。
Avatar
Avatar
yutailang0119
@trickart @koogawa Digital LoungeでAppleのエンジニアが言ってたけど、ユーザーのinputをMarkdown解析することは、ユースケースとしては考えていないっぽい感じでした
なるほどー!
Avatar
yutailang0119 7/6/2021 11:28 AM
AsyncImageのバックポートを作ったので、使ってみてほしい! https://github.com/yutailang0119/SBPAsyncImage
Contribute to yutailang0119/SBPAsyncImage development by creating an account on GitHub.
👏🏼 6
11:28 AM
macOSでscale対応できてないので、誰か助けて https://github.com/yutailang0119/SBPAsyncImage/issues/8
Avatar
そういえばこの前発見した、SwiftUIのScrollViewReaderで最初のRowが一つもないSessionをIDに飛ばそうとすると落ちる問題、Xcode 13 beta 2でもまだ発生していますね https://github.com/el-hoshino/ScrollViewReaderDemo ↑こちらのリポをビルドして、上のボタンをタップすると、ABCDFをタップすれば該当のセッションに遷移しますが、Eだけクラッシュします
A demo of using ScrollViewReader. Contribute to el-hoshino/ScrollViewReaderDemo development by creating an account on GitHub.
Avatar
ちょっと面白いのは、macOSならこの問題発生しないですよね、iOSだけ
Avatar
Miwa / Ensan 8/18/2021 2:27 PM
TextFieldのイニシャライザ、init<S: StringProtocol>(_ title: S, text: Binding<String>)が優先されてinit(_ title: LocalizedStringKey, text: Binding<String>)が優先されなくなった?
2:27 PM
Xcode13のbeta5です
2:30 PM
beta3の時点でもそうなってるな (edited)
Avatar
swiftuiとspritekitで簡易なゲームを作っています。タイトル画面からゲーム画面への遷移にはpresentSceneを使っており遷移自体はうまくいくのですが、アプリを一度バックグラウンドにするとゲーム中でもタイトル画面に戻ってしまいます。おそらくContentViewで毎回タイトル画面が呼び出されているせいだと思うのですが改善方法が調べてもわからず詰まっています。どなたか解決策をご存じの方はいらっしゃいますか?
4:40 AM
import SwiftUI import SpriteKit struct ContentView: View { var scene: SKScene { let scene = GameTitleScene() scene.size = CGSize(width: UIScreen.main.bounds.size.width , height: UIScreen.main.bounds.size.height) return scene } var body: some View { SpriteView(scene: scene) .frame(width: UIScreen.main.bounds.size.width , height: UIScreen.main.bounds.size.height) .ignoresSafeArea() } } struct ContentView_Previews: PreviewProvider { static var previews: some View { ContentView() } }
4:40 AM
ContentViewはこんな感じになっています
Avatar
Avatar
kihara
swiftuiとspritekitで簡易なゲームを作っています。タイトル画面からゲーム画面への遷移にはpresentSceneを使っており遷移自体はうまくいくのですが、アプリを一度バックグラウンドにするとゲーム中でもタイトル画面に戻ってしまいます。おそらくContentViewで毎回タイトル画面が呼び出されているせいだと思うのですが改善方法が調べてもわからず詰まっています。どなたか解決策をご存じの方はいらっしゃいますか?
このコードだと body が走る度に scene が再生成されてしまうので、 @StateObject の中に scene を持たせるなどした方が良いと思います。たとえば↓のように(これで問題が解決するかわかりませんが)。 struct ContentView: View { @StateObject private var state: ContentViewState = .init() var body: some View { SpriteView(scene: scene) .frame(width: UIScreen.main.bounds.size.width , height: UIScreen.main.bounds.size.height) .ignoresSafeArea() } } import Combine final class ContentViewState: ObservableObject { let scene: SKScene init() { self.scene = GameTitleScene() scene.size = CGSize(width: UIScreen.main.bounds.size.width , height: UIScreen.main.bounds.size.height) } } (edited)
4:52 AM
もしくは、 SpriteViewUIViewControllerRepresentable 等であればそちらで持たせる方が良いかもしれません。
4:53 AM
↑の例では StateObjectobjectWillChange が活きてませんが、他の色々があると仮定して持たせるならそこかなと。
Avatar
SpriteView は SwiftUI 標準ですね。koher さんの回答と似たような感じですが、単に元のコードから scene@State な stored プロパティにするだけでも解決する気がします。
Avatar
Avatar
kebo
SpriteView は SwiftUI 標準ですね。koher さんの回答と似たような感じですが、単に元のコードから scene@State な stored プロパティにするだけでも解決する気がします。
そういえば追加されたんでしたね💦 他に状態を色々扱わないのであれば @State でも良さそうですね。
Avatar
お二方とも解答ありがとうございます。
5:06 AM
import SwiftUI import SpriteKit struct ContentView: View { @StateObject private var state: ContentViewState = .init() var body: some View { SpriteView(scene: state.scene) .frame(width: UIScreen.main.bounds.size.width , height: UIScreen.main.bounds.size.height) .ignoresSafeArea() } } struct ContentView_Previews: PreviewProvider { static var previews: some View { ContentView() } }
5:06 AM
import Combine import SwiftUI import SpriteKit final class ContentViewState: ObservableObject { @Published var scene: SKScene init() { self.scene = GameTitleScene() scene.size = CGSize(width: UIScreen.main.bounds.size.width , height: UIScreen.main.bounds.size.height) } }
5:06 AM
上記のようにコードを更新しましたが解決に至りませんでした
5:08 AM
そもそもscene自体が更新されていないんでしょうか?
Avatar
これは原因ではないと思いますが、 scene@Published で保持する必要ないですね。 SKScene はクラスで、そのインスタンスを差し替えるような場合にしか @Published は意味がないです。なので、普通に let で保持すれば良いと思います。 @StateObject にしたのはライフサイクルのためなので。
5:10 AM
アプリを一度バックグラウンドにするとゲーム中でもタイトル画面に戻ってしまいます
これは scene が初期化されてしまったためでしょうか?それであれば、 ContentView の上位に原因がある可能性があるかと思います。
Avatar
Avatar
koher
これは原因ではないと思いますが、 scene@Published で保持する必要ないですね。 SKScene はクラスで、そのインスタンスを差し替えるような場合にしか @Published は意味がないです。なので、普通に let で保持すれば良いと思います。 @StateObject にしたのはライフサイクルのためなので。
なるほど、勉強になりますありがとうございます。
Avatar
Avatar
koher
アプリを一度バックグラウンドにするとゲーム中でもタイトル画面に戻ってしまいます
これは scene が初期化されてしまったためでしょうか?それであれば、 ContentView の上位に原因がある可能性があるかと思います。
bodyで呼び出すsceneをゲーム画面のものにしたときはバックグラウンドにしても途中から再開されるのでsceneが初期化されているわけではないと思っているのですが
Avatar
presentSceneってSKViewのメソッドだと思うのですが、どうやって呼び出しているのでしょうか?
Avatar
Avatar
kihara
bodyで呼び出すsceneをゲーム画面のものにしたときはバックグラウンドにしても途中から再開されるのでsceneが初期化されているわけではないと思っているのですが
ということは複数の SKScene があってインスタンスを切り替えているということですか?
Avatar
GameTitleScene の中で override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) { if self.atPoint(self.firstTouch).name == "start" { guard let t = touches.first else { return } self.lastTouch = t.location(in: self) let touchPoint = self.atPoint(self.lastTouch) if touchPoint.name == "start" { self.startButton.fillColor = UIColor.clear self.view?.presentScene(GameScene(size: self.size)) } } } のように呼び出しています
Avatar
Avatar
koher
ということは複数の SKScene があってインスタンスを切り替えているということですか?
そうですね
Avatar
おお。そんな抜け道が。
6:01 AM
もしかしたらバックグラウンドから戻る時にSpriteViewが内部状態として抱えてるSKViewはリセットされて、Stateとしてもってるsceneから再構築されるけどそのsceneは画面遷移前のtitleScene、みたいな感じかもしれませんね (edited)
Avatar
なるほど、、、
Avatar
Xcode Previews でダークモードの見た目を確認するのに .environment(\.colorScheme, .dark) を使っているんですが、同じ用にハイコントラストモードの見た目を確認したくて .environment(\.colorSchemeContrast, .increased) のようにすると Key path value type 'WritableKeyPath<EnvironmentValues, ColorSchemeContrast>' cannot be converted to contextual type 'KeyPath<EnvironmentValues, ColorSchemeContrast>' となって、 API リファレンスを確認すると
Your app cannot override the user’s choice.
https://developer.apple.com/documentation/swiftui/colorschemecontrast とあり、どうにもならなそうです。 Xcode Previews でハイコントラストモードの見た目を確認(一覧表示)するのに、何か他に良い手段をご存知の方いますか?
(edited)
Avatar
Kishikawa Katsumi 9/9/2021 7:05 PM
UIAccessibilityContrast.high に相当するのがあるかと思ったけどなさそうだった。ないのかな。
Avatar
ColorSchemeContrast.increased があるんですが、 "Your app cannot override the user’s choice." で set できないようです。
Avatar
Kishikawa Katsumi 9/9/2021 7:08 PM
ColorSchemeContrast.init?(UIAccessibilityContrast) のイニシャライザがあるみたいなのでそれに UIAccessibilityContrast.high 渡したら動いたりしないかな。
7:08 PM
ドキュメントしか見てないので全然ダメかもしれない。
Avatar
ColorSchemeContrast.increased 自体は取得できるんですが、 .environment(\.colorSchemeContrast, .increased) しようとすると https://developer.apple.com/documentation/swiftui/environmentvalues/colorschemecontrast が read-only なので上書きできない状況ですね。 (edited)
Avatar
Kishikawa Katsumi 9/9/2021 7:15 PM
あ、なるほど。左辺が問題なんすね。
Avatar
そうなんです😭
7:16 PM
"Your app cannot override the user’s choice." の理念はわかるんですが、 Previews では上書きさせてほしい・・・。
Avatar
Avatar
koher
"Your app cannot override the user’s choice." の理念はわかるんですが、 Previews では上書きさせてほしい・・・。
Kishikawa Katsumi 9/14/2021 4:33 PM
ContentView() .environment(\.colorScheme, .dark) .environment(\._colorSchemeContrast, .increased) Underscore付けたらセットできました。。。
Avatar
おおお、そんな裏技が!
Avatar
Kishikawa Katsumi 9/14/2021 4:34 PM
ただ、.lightあるいは無指定で_colorSchemeContrastだけセットしてもAny/High Contrastにならないですね。これはよくわからない。DarkはHigh Contrastの色になった。
Avatar
え、それは謎ですね・・・。
Avatar
Kishikawa Katsumi 9/14/2021 4:34 PM
@Gernot @Sommer Even better, there’s _colorSchemeContrast that is writable
4:35 PM
light/increasedの問題は私のところだけかもしれない。
4:35 PM
Assetの方がなんか違うのかな。。。
Avatar
試してみたところ、こっちの環境ではライトモードでもちゃんとハイコントラストになりました。 (edited)
4:42 PM
Asset Catalog 上は Any/High Contrast です。 (edited)
Avatar
Kishikawa Katsumi 9/14/2021 4:49 PM
こういうAssetがあって
4:49 PM
こうなるんですよねえ。
4:56 PM
一応コード struct ContentView: View { var body: some View { VStack { Text("Hello, world!") .padding() .background(Color("sampleColor")) .environment(\.colorScheme, .light) Text("Hello, world!") .padding() .background(Color("sampleColor")) .environment(\.colorScheme, .dark) Text("Hello, world!") .padding() .background(Color("sampleColor")) .environment(\.colorScheme, .light) .environment(\._colorSchemeContrast, .increased) Text("Hello, world!") .padding() .background(Color("sampleColor")) .environment(\.colorScheme, .dark) .environment(\._colorSchemeContrast, .increased) } } }
Avatar
うーん、なんでだろう?ただ、複数の colorSchemeContrast が混ざるのはさすがにおかしい気がして、僕がやっているのは previews の中でそれぞれ指定した場合(プレビュー上で別の iPhone に表示される場合)ですね。
Avatar
Kishikawa Katsumi 9/14/2021 5:03 PM
1つのラベルとPreviewで変更、っていう場合でも変わらずで。 上のは現象を示そうと思ってまとめたんですよね。
Avatar
Avatar
Miwa / Ensan
TextFieldのイニシャライザ、init<S: StringProtocol>(_ title: S, text: Binding<String>)が優先されてinit(_ title: LocalizedStringKey, text: Binding<String>)が優先されなくなった?
Miwa / Ensan 9/15/2021 9:45 AM
これが結局RC版でも直っていないので、TextFieldに指定している文字列リテラルが単なるStringと解釈されてしまって、ローカライズが効かなくなるパターンが増えそうな気がします
9:45 AM
iOS15がavailableな場合とそうでない場合で選ばれるイニシャライザが変わる struct ContentView: View { @State private var text = "" var body: some View { if #available(iOS 15, *) { TextField("foo", text: $text) // "foo"はLocalizedStringKey扱い } else { TextField("foo", text: $text) // "foo"はString扱い } } } (edited)
Avatar
iOS15以前のSDKだと、こうなってたのが public init(_ titleKey: LocalizedStringKey, text:, onEditingChanged:) @_disflavoredOverload public init<S: StringProtocol>(_ title: S, text:, onEditingChanged:) iOS15から // 1 @_disflavoredOverload public init(_ titleKey: LocalizedStringKey, text:, onEditingChanged:) // 2 @_disflavoredOverload public init<S: StringProtocol>(_ title: S, text:, onEditingChanged:) // 3 @available(iOS 15.0) public init(_ titleKey: LocalizedStringKey, text:, prompt:) // 4 @_disflavoredOverload @available(iOS 15.0) public init<S: StringProtocol>(_ title: S, text:, prompt:) こうなった。 (edited)
11:09 AM
で、 @available(iOS 15.0) を満たさない場合、 _disflavoredOverload 同士の戦いになって、スコアが同じなので_disflavoredOverload で勝敗を捻じ曲げる前の本来のオーバーロード解決で評価されて、 2が勝ってるんですね。 (edited)
11:10 AM
ヤバい
Avatar
_disflavoredOverloadにもランクが必要なやつだ
Avatar
ランクわかるほしいわかる
Avatar
人間に管理できるものじゃなくなる…
11:47 AM
人間やめて型推論器になるしか無い
Avatar
様々なルールの中で優先度が暗黙的に決まって、その末項に_disfavoredOverloadがあるよりは、一番手前のルールとして人間が明示的に決める、としたのなら、どうなんだろう
11:49 AM
管理できるようになるのかな
Avatar
どうなんだろう
11:52 AM
とりあえず今のオーバーロード解決器は減点方式だから色々手を入れないといけなさそう
Avatar
SwiftUIのこの問題を引き合いに出して、overloadの問題何とかして欲しいよと言う議論を出すことは、出来そうだね
Avatar
あー、でも普通のルールの中の選択肢と比べた時に無条件で勝つから、現状のスコアベースの判定に統合する必要はなくて、人間が決めた優先度だけで解決すればいいから意外といけそう?
Avatar
そうそう
11:57 AM
まあでも返り値の判定との兼ね合いとかも出るから、上手く行くとも限らないのかな
Avatar
長文FB送っておいた。FB9626955
👏🏻 7
Avatar
TextFieldのinitの問題についてのフィードバックに返信が来て、iOS15.2/iPadOS15.2のbetaで解消したよ、と言われました。OSの方で解決するのか・・・
😳 1
Avatar
Xcode 13.2でiOS 13からSwift Concurrencyが使えるようになったけど、SwiftUIでConcurrencyを使うのに便利なtask modifierがiOS 15からなのでちょっと困る。extensionで互換API作るとするとこんな感じかな?
1:32 AM
extension View { func task( priority: TaskPriority = .userInitiated, _ action: @Sendable @escaping () async -> Void ) -> some View { var task: Task<Void, Never>? return self .onAppear { task = Task(priority: priority) { await action() } } .onDisappear { task?.cancel() } } }
1:34 AM
body が走る度に走ってうまくいかなさそうな気がしてきた・・・。
Avatar
質問失礼いたします。 ObservableObjectの普通のプロパティが$ob.propertyでアクセスすると、Bind<PropertyType>として扱えるのは、ObservableObject.Wrapperの仕組みと認識しているのですけれど、この辺りについてのドキュメントなどご存知の方いたら教えてほしいです。少し探したのですけれど、見当たらず… (もしくはこの認識が間違っていたら教えてほしいです) https://developer.apple.com/documentation/swiftui/observedobject/wrapper
2:39 AM
KeyPath member lookup と組み合わせで動いてますね。
Avatar
propertyWrapperのprojectedValueとKeyPath Dynamic Member Lookupのあわせ技な部分なので難しいですよね。 $obObservableObject.Wrapperが返ってくるところまでは理解されていると思います。これはpropertyWrapperのprojectedValue の仕組みでObservableObject<T>.Wrapper が返ってきているためです。 このWrapperにはさらにKeyPath Dynamic Member Lookupが実装されており、Tに生えてる任意のプロパティを https://developer.apple.com/documentation/swiftui/observedobject/wrapper/subscript(dynamicmember:) を経由して取り出しています。見え方が普通のプロパティアクセスになっているところが特殊です。
Avatar
ありがとうございます!資料の方読んでみます 実際にObservableObject.Wrapperこの場合はStateObject<Type>と認識してます ←ここを勘違いしてました、ObservableObject<Type>.Wrapperが正ですね)にプロパティが存在するわけではなく、動的にプロパティを生成して存在しているように見せているという感じなのですね…! (edited)
t_desune 1
Avatar
$ob.property を省略を減らして書くと _ob.projectedValue[dynamicMember: \.property] という感じになります
t_naruhodo 1
Avatar
ObservedObjectObservableObject が混ざってしまっている気がしますので(両者は別物なので)ご注意下さい。
Avatar
本当ですね、ありがとうございます シンプルに書けるんですけれど、ボトムアップで理解しようとするといろいろと複雑ですね…
Avatar
そうですね。 Property Wrapper と KeyPath Member Lookup の合わせ技で、さらに登場人物( ObservedObject, StateObject, Binding, ObservableObject, Published )も多いのでこの辺りややこしいです。
Avatar
omochimetaru 2/17/2022 2:59 AM
しかもこれやるためにこの言語仕様が産まれたし・・・
Avatar
URL を返す async 関数を Task のクロージャで await するようなボタンを Xcode Previews 上でクリックしたら必ずプレビューのプロセスがクラッシュする (実機だと問題なし) という謎の問題を発見して,特に困っているわけではないのですが気になるので情報をお持ちの方を探しています. 以下ミニマル再現コードです.(macOS 12.3 & Xcode 13.3 や iPadOS 15.4 & Swift Playgrounds 4.0.2 で再現することを確認) import SwiftUI func foo() async -> URL { URL(fileURLWithPath: "/") // 戻り値が String 等では問題なし } struct ContentView: View { var body: some View { Button("Press Me") { Task { _ = await foo() } } } } スタックトレース: Thread 4 Crashed:: Dispatch queue: com.apple.root.user-initiated-qos.cooperative 0 libsystem_kernel.dylib 0x1cba39e60 __pthread_kill + 8 1 libsystem_pthread.dylib 0x1cba8d3c0 pthread_kill + 256 2 libsystem_c.dylib 0x1801023f4 abort + 124 3 libswift_Concurrency.dylib 0x1cb9db604 swift::swift_Concurrency_fatalError(unsigned int, char const*, ...) + 12 4 libswift_Concurrency.dylib 0x1cb9dd350 swift_task_dealloc + 124 5 ContentView.1.preview-thunk.dylib 0x104a2b06c (1) await resume partial function for closure #1 in closure #1 in ContentView.__preview__body.getter + 80 (ContentView.swift:17) 6 ContentView.1.preview-thunk.dylib 0x104a2b5d9 (1) await resume partial function for thunk for @escaping @callee_guaranteed @Sendable @async () -> (@out A) + 1 7 ContentView.1.preview-thunk.dylib 0x104a2b73d (1) await resume partial function for partial apply for thunk for @escaping @callee_guaranteed @Sendable @async () -> (@out A) + 1 8 libswift_Concurrency.dylib 0x1cb9dced1 completeTaskWithClosure(swift::AsyncContext*, swift::SwiftError*) + 1 (edited)
Avatar
@preconcurrency import Foundation 書いたら変化しますか?
Avatar
変化しないですね.Foundation.URL が使われているのか SwiftUI.URL が使われているのかわからないので念のために @preconcurrency import struct Foundation.URL import struct SwiftUI.Button import protocol SwiftUI.PreviewProvider import protocol SwiftUI.View みたいなのも試してみましたが,こうするとファイルを開き直した時にプレビューが表示されなくなっちゃうのでプレビューが残っている間に試しました. ただ,-warn-concurrency つけていても警告は出ていなかったのであんまり関係ないかなという気はしてます. ちなみに foo() の定義だけ別のファイルに持っていくとクラッシュしなくなるんですよね. (edited)
Avatar
Sendable かどうかでなにか変わってるのかと思いましたが、関係ないようですね。不思議。
Avatar
一応試した範囲で foo の戻り値の型で再現する/しない型を書きだすとこんな感じですね.Foundation の struct だと再現するのかと思いきや例外もあってそうとも限らなさそうでなんとも... 再現する型: URL, URLError, UUID, Date, Measurement, Locale, some SwiftUI.View 再現しない型: String, [Int], [String: Int], some StringProtocol, DispatchQueue, Bundle, Data, DateFormatter, URLSession, JSONEncoder, NSNumber, CGFloat, Decimal, SwiftUI.Text (edited)
Avatar
普通にシミュレータで実行した時再現しますか?
10:19 AM
あー、x64のコード生成がおかしいのかと思ったんですがiPadでも再現するなら違うか…
Avatar
シミュレータでは問題なかったと記憶してますが,コードが違ったかもしれないので明日もう一回確かめてみます.(ただおっしゃる通り x64 向けのコンパイルに限った問題ではないです)
Avatar
やっぱりシミュレータではクラッシュしないですね.ちなみに私の環境だと M1 Mac mini なのでプレビューやシミュレータも arm64 でした.
Avatar
rei.nakaoka 8/5/2022 7:51 AM
質問失礼します。 以下を参考にSwiftUIのNavigationLinkで複数回画面遷移をした後に一気に最初の画面に戻る実装をしています。記事ではNavigationLinkに.isDetailLink(false)をつけなければ最初のView戻ることはできないと言及されています。 しかし、.isDetailLink(false)をつけない場合や、.isDetailLink(true)にした場合でも同様に最初のViewに戻ることができました。これは、SwiftUIの仕様変更等が原因なのでしょうか??何かご存知の方がいられましたらご教授いただければ幸いです。 https://www.yururiwork.net/archives/148 https://stackoverflow.com/questions/57334455/how-can-i-pop-to-the-root-view-using-swiftui/59662275#59662275 以下ソースコードです。 struct FirstView: View { @State private var isActive = false var body: some View { NavigationView { NavigationLink(destination: SecondView(isFirstViewActive: $isActive), isActive: $isActive) { Button(action: { self.isActive = true }, label: { Text("Forward to Second View.") }) } .navigationBarTitle("First View") } } } struct SecondView: View { @State private var isActive = false @Binding var isFirstViewActive: Bool var body: some View { NavigationLink(destination: ThirdView(isFirstViewActive: $isFirstViewActive), isActive: $isActive) { Button(action: { self.isActive = true }, label: { Text("Forward to Third View.") }) } .isDetailLink(false) //ここをコメントアウトしてもFirstViewに戻れる。 .navigationBarTitle("Second View") } } struct ThirdView: View { @Binding var isFirstViewActive: Bool var body: some View { Button(action: { self.isFirstViewActive = false }, label: { Text("Back to First View.") }) .navigationBarTitle("Third View") } } (edited)
Finally now with Beta 5 we can programmatically pop to a parent View. However, there are several places in my app where a view has a "Save" button that concludes a several step process and
Avatar
Kishikawa Katsumi 8/5/2022 1:38 PM
動作のキモの部分はNavigationLinkのisActiveをBindingとして受け渡していって奥のビューで全部一気にfalseに書き換えるということのようなので、isDetailLink(false)でないと動かなかったのはSwiftUIのバグか内部でなんらかの変更があったのだと思います。 初期のSwiftUIの環境(iOS 13)では確かにisDetailLink(false)になってなければRootには戻らないようですね。
t_naruhodo 1
t_kansha 1
Avatar
質問失礼します。同じ内容が既に出てましたらすみません。 現象から言うと、M1+下記の環境でフレームワークをターゲットにした時にキャンバスでプレビューができません。 Xcode13.4.1を使用しており、アプリ本体のターゲットAとフレームワークのターゲットBがあるとします。特筆する環境としてEXCLUDED_ARCHITECTURESにiOS Simulatorのarm64を全てのターゲットで指定しています。この状態でスキームをBにしてキャンバスを使用するとarm64のバイナリが必要とエラーが出てプレビューできません。 スキームをAにした場合はプレビュー可能なのですが、全体ビルド量が多く時間がかかるためBにしています。また導入しているライブラリの関係でシミュレータのarm64のビルドができずこのような設定にしています。 XcodeとシミュレータをRosettaで開くで試してみましたが、特に変化はありませんでした。 解決策orワークアラウンド等ありませんでしょうか😩 足りない情報等ありましたら質問していただけたらと思います! よろしくお願いします。
Avatar
Xcode Previewsは基本的にiOS Simulator上で動いているので EXCLUDED_ARCHITECTURES にiOS Simulatorターゲットを指定してしまうと基本的に動かないのではないかと思います。(なぜアプリターゲットなら動くのかはわかりませんが…) 最小再現プロジェクトを提供していただけるとスムーズにトラブルシュートできると思います 🙏
Avatar
承知しました!プロジェクト作成してまいります🫡
Avatar
Avatar
Yuta Saito
Xcode Previewsは基本的にiOS Simulator上で動いているので EXCLUDED_ARCHITECTURES にiOS Simulatorターゲットを指定してしまうと基本的に動かないのではないかと思います。(なぜアプリターゲットなら動くのかはわかりませんが…) 最小再現プロジェクトを提供していただけるとスムーズにトラブルシュートできると思います 🙏
こちら再現プロジェクトのリポジトリになります。
1:10 PM
Contribute to dazy1030/SwiftUIQuestionSample development by creating an account on GitHub.
🙏 1
Avatar
問題再現できました。Preview用にビルドしたFrameworkのアーキテクチャに関わらず、それをロードするPreview表示用のアプリがarm64向けになってしまうXcode Previewsの問題じゃないかと思います。ワークアラウンドとしては、メインのアプリターゲットとは別にEXCLUDED_ARCHITECTURESを指定したPreview用のアプリターゲットを作るのがよさそうです。
6:41 PM
ちなみにx64 onlyアプリターゲットの場合、自動でRosetta経由で起動するっぽいですね。
Avatar
遅い時間までありがとうございます😭
Xcode Previewsの問題じゃないか
であればXcodeのアップデートに期待したいですね!
Preview用のアプリターゲットを作る
たしかに…!フレームワークを指定していたのはビルド時間を削減して開発体験の向上を狙うことが目的だったので、この方法で十分そうです。 ひとまずアプリターゲットを増やす方法を試してみようと思います。ありがとうございました!🙇‍♂️
👍 1
2:20 AM
(アーキテクチャを実行中に取得する方法があるのか。なるほど)
Avatar
https://twitter.com/noppefoxwolf/status/1575206701590286336?s=21&t=G0ANhf7bMCkZC9iJV7RT0w SwiftUIでホロカードの再現やってみたんですけど、SwiftUIの.blendModeが強力でこういうことするのに向いてますね。 iOSのUIKitでやるとすごい大変だと思う。
3D holographic card effect #SwiftUI
👀 3
👍 1
4:38 AM
vividLightみたいなブレンドも .blendMode(.colorBurn) .blendMode(.colorDodge) って感じで重ねがけできて、簡易的なシェーダービルダーDSLとしての側面もあるなという感じ
Avatar
便利…
Avatar
Avatar
kebo
URL を返す async 関数を Task のクロージャで await するようなボタンを Xcode Previews 上でクリックしたら必ずプレビューのプロセスがクラッシュする (実機だと問題なし) という謎の問題を発見して,特に困っているわけではないのですが気になるので情報をお持ちの方を探しています. 以下ミニマル再現コードです.(macOS 12.3 & Xcode 13.3 や iPadOS 15.4 & Swift Playgrounds 4.0.2 で再現することを確認) import SwiftUI func foo() async -> URL { URL(fileURLWithPath: "/") // 戻り値が String 等では問題なし } struct ContentView: View { var body: some View { Button("Press Me") { Task { _ = await foo() } } } } スタックトレース: Thread 4 Crashed:: Dispatch queue: com.apple.root.user-initiated-qos.cooperative 0 libsystem_kernel.dylib 0x1cba39e60 __pthread_kill + 8 1 libsystem_pthread.dylib 0x1cba8d3c0 pthread_kill + 256 2 libsystem_c.dylib 0x1801023f4 abort + 124 3 libswift_Concurrency.dylib 0x1cb9db604 swift::swift_Concurrency_fatalError(unsigned int, char const*, ...) + 12 4 libswift_Concurrency.dylib 0x1cb9dd350 swift_task_dealloc + 124 5 ContentView.1.preview-thunk.dylib 0x104a2b06c (1) await resume partial function for closure #1 in closure #1 in ContentView.__preview__body.getter + 80 (ContentView.swift:17) 6 ContentView.1.preview-thunk.dylib 0x104a2b5d9 (1) await resume partial function for thunk for @escaping @callee_guaranteed @Sendable @async () -> (@out A) + 1 7 ContentView.1.preview-thunk.dylib 0x104a2b73d (1) await resume partial function for partial apply for thunk for @escaping @callee_guaranteed @Sendable @async () -> (@out A) + 1 8 libswift_Concurrency.dylib 0x1cb9dced1 completeTaskWithClosure(swift::AsyncContext*, swift::SwiftError*) + 1 (edited)
だいぶ前ですが,この問題は Swift 5.7 で修正されてそうです.
Avatar
리이오(リ-オ) 12/30/2022 1:10 AM
このようなgridviewを実装したいのですが、どうすればいいですか?
Avatar
Kishikawa Katsumi 12/30/2022 2:46 AM
質問の範囲が広くて答えるの難しいですね。もう少し具体的に実際にこういう結果になって困っているとか書きかけのコードとか見せられませんか?
Avatar
0 に近いときに青い部分が残ってしまうんですけど、こういう仕様なんですっけ?
Avatar
仕様かわからないですけど、仕様としては綺麗な気がしますね。回線が遅くてもダウンロードが始まったのかどうかをプログレスだけで表現出来るのは良さそう
Avatar
なるほど・・・。そうだとしても、その機能の ON/OFF を設定できるといいんですけどね。もしかするとあるのかもしれませんが、軽く探した限りでは見つけられず。
4:10 AM
制限時間の残り時間を表すのに(値を減らす方向で)使ってたんですが、そうするとタイムリミット付近で滑らかに動かず。 progress でないものに ProgressView を使うのが良くないのかもしれませんが。
Avatar
そういうのを表すなら iOS 16 で追加された Gauge の方が良いかもしれないですね.あるいは ProgressViewStyle を自作するなど.
Avatar
例でもバッテリー残量を表すのに使われていますし,実際に試してみても 0 付近であってもリニアな推移を示しました. https://developer.apple.com/documentation/swiftui/gauge
Avatar
Avatar
kebo
そういうのを表すなら iOS 16 で追加された Gauge の方が良いかもしれないですね.あるいは ProgressViewStyle を自作するなど.
ありがとうございます。なるほど、意味的にそちらの方が適していそうですね。 Gauge の方がかなり太いので .gaugeStyle を試していたら、 .accessoryLinearCapacityProgressView と同じ細さになったんですが、 ProgressView 同様に 0 付近でリニアじゃなくなってしまいました😂 GaugeStyle の名前から考えると、 ProgressView で 0 付近で見えているのは accessory なのかもしれないですね。 0 で消えるのは謎ですが。
👀 1
Avatar
どうやら UIKit の UIProgressView に原因がありそうですね.
Avatar
10 年くらい前 (iOS 6 の頃) にはすでに話題になってた問題みたいです. https://stackoverflow.com/questions/16491887/uiprogressview-with-progress-at-low-values-issue (edited)
I'm running a progress bar which updates every second. It needs to run for upwards of 300 seconds so the changes in the progress bar's 'progress' variable are on the magnitude of about 0.003 per se...
Avatar
おお、知られた問題(?)だったんですね!そして↓の回答に笑いました。
I decided to simply forget UIProgressView altogether and write a fake one leveraging UISlider instead. I removed the knob with [setThumbImage:[UIImage new] forState:UIControlStateNormal] and disabled user interaction.
t_kusa 2
Avatar
Kishikawa Katsumi 4/10/2023 8:51 AM
https://forums.swift.org/t/beta-build-swiftui-interfaces-visually/64303 SketchみたいなUIでSwiftUIのビューを構築してコードをエクスポートできるアプリが開発中らしい。TestFlightでインストールして試せる。開発してる人はかなり腕ききの人だから期待できそう。
We are working on a tool that allows building SwiftUI visually, export clean and structured SwiftUI code and integrate it into any Xcode project Join our TestFlight group - we'd love your feedback!
👀 8
Avatar
ツイッターのこの会話が発端の話題なんですが https://twitter.com/omochimetaru/status/1652273418958675969
@kntkymt @treastrain @fummicc1 ContentViewAって、 ContentViewBのように書いてるのと(ほぼ)同じって理解であってますか?
12:36 AM
// 素早くトグルすれば値が生きている struct ContentViewE: View { @State var toggle = false var body: some View { if toggle { VStack { TextView() Button { toggle.toggle() } label: { Text("toggle if") } } } else { VStack { TextView() Button { toggle.toggle() } label: { Text("toggle if") } } } } } final class ViewModel: ObservableObject { @Published var count = 0 } struct TextView: View { @StateObject var viewModel: ViewModel init() { _viewModel = .init(wrappedValue: ViewModel()) } var body: some View { Button { viewModel.count += 1 } label: { Text("count: \(viewModel.count)") } } }
🙇‍♂️ 1
12:36 AM
↑このように、 if 文の then側とelse側にそれぞれViewを置いて、その中にStateObjectをもたせた時、
12:37 AM
素早く then → else → then と戻ってくると、過去のthenの値が記憶されているが (StateObjectがdeinitされない)
12:37 AM
ゆっくり then → else → then と戻ってくると、値がリセットされる (StateObjectが deinit & init される)
12:37 AM
という現象に遭遇したんですが、これってSwiftUIのバグですか? それとも、SwiftUI的に何か説明されてる現象ですか?
12:38 AM
↓素早く操作すると値が残っている現象の動画です (edited)
12:38 AM
ちなみに、このように書き換えると、値が残る現象が消える事もわかりました。 // 必ずリセットされる struct ContentViewD: View { @State var toggle = false var body: some View { VStack { if toggle { TextView() } else { TextView() } Button { toggle.toggle() } label: { Text("toggle if") } } } }
Avatar
Miwa / Ensan 4/30/2023 2:16 AM
StateObjectのinit(wrappedValue:)を自前で呼ぶべきではない、って話があった気がするんですが、それとは関係なさそうですか?
Avatar
omochimetaru 4/30/2023 2:18 AM
あ〜関係ありそう・・・!
2:19 AM
https://developer.apple.com/documentation/swiftui/stateobject class DataModel: ObservableObject { @Published var name = "Some Name" @Published var isEnabled = false } struct MyView: View { @StateObject private var model = DataModel() // Create the state object. var body: some View { Text(model.name) // Updates when the data model changes. MySubView() .environmentObject(model) } }
2:19 AM
↑こういう書き方がお作法なんですね。
2:22 AM
struct TextView: View { @StateObject var viewModel = ViewModel() var body: some View { Button { viewModel.count += 1 } label: { Text("count: \(viewModel.count)") } } } ↑こう書き換えてみましたが、現象には変化無かったです。
t_naruhodo 1
Avatar
ちなみにですが
StateObjectのinit(wrappedValue:)を自前で呼ぶべきではない
これは最近ドキュメントが更新され、「Initialize state objects using external data」のセクションが追加され 「initを読んでも良いが、StateObjectが状態を復元するから、初期化は最初しか走らないよ(意訳)」と言う文面に変わりました https://developer.apple.com/documentation/swiftui/stateobject#Initialize-state-objects-using-external-data
A property wrapper type that instantiates an observable object.
👀 3
2:25 AM
が、今回の例でやる必要かなったですね(↑の最初のコード作ったの自分です)
Avatar
body の中で if を使うと条件式の値が変わった時に画面のライフタイム的にも一度画面が消えて再表示された扱いになる (onDisappear が呼ばれて onAppear が呼ばれ直す) ので,画面のライフタイムと一致するはずの @StateObject は初期化される方が正しい挙動な気がします.これを避けるために私はある程度複雑な View では body の中で if/switch をトップレベルで使わないように VStack で囲んだりしてますね.
😿 1
Avatar
omochimetaru 4/30/2023 2:29 AM
ふむふむ。やはり値が生き残る挙動の方が変なんですね。
Avatar
Avatar
kebo
body の中で if を使うと条件式の値が変わった時に画面のライフタイム的にも一度画面が消えて再表示された扱いになる (onDisappear が呼ばれて onAppear が呼ばれ直す) ので,画面のライフタイムと一致するはずの @StateObject は初期化される方が正しい挙動な気がします.これを避けるために私はある程度複雑な View では body の中で if/switch をトップレベルで使わないように VStack で囲んだりしてますね.
omochimetaru 4/30/2023 2:29 AM
body の中で if/switch をトップレベルで使わないように VStack で囲んだりしてます
これってどういう意味ですか? VStackの中で if を使ったら、そのif配下においては同じ話ですよね?
2:30 AM
// 素早くトグルすれば値が生きている struct ContentViewE2: View { @State var toggle = false var body: some View { VStack { if toggle { VStack { TextView() Button { toggle.toggle() } label: { Text("toggle if") } } } else { VStack { TextView() Button { toggle.toggle() } label: { Text("toggle if") } } } } } }
2:30 AM
↑例えばこのようにしても同じ現象が起こります。 if があるのは 「トップレベル」でこそないですが、同じです。 (edited)
Avatar
こういうのがあった場合に,前者の場合だと condition が変わるたびに ContentViewA に対応づく画面は一度非表示になって再度表示され直した扱いになりますが,後者の場合はそうはならないという意味です.ifelse のブロックの中に書いたものは条件が変わるたびに一度死にますよ. struct ContentViewA: View { var body: some View { if condition { Text("A") } else { Text("B") } } } struct ContentViewB: View { var body: some View { VStack { if condition { Text("A") } else { Text("B") } } } }
Avatar
Avatar
kebo
こういうのがあった場合に,前者の場合だと condition が変わるたびに ContentViewA に対応づく画面は一度非表示になって再度表示され直した扱いになりますが,後者の場合はそうはならないという意味です.ifelse のブロックの中に書いたものは条件が変わるたびに一度死にますよ. struct ContentViewA: View { var body: some View { if condition { Text("A") } else { Text("B") } } } struct ContentViewB: View { var body: some View { VStack { if condition { Text("A") } else { Text("B") } } } }
omochimetaru 4/30/2023 2:36 AM
ContentViewAでもContentViewBでも、 condition が変わったら Text("A")Text("B") の表示/非表示が切り替わるのではないのでしょうか (edited)
Avatar
omochimetaru さんのコード例で言うと,条件が変わった場合に ContentViewE2 は生き続けますが TextView は死にます.body 直下の VStack がなかった場合は ContentViewE2 も死にます. (edited)
Avatar
omochimetaru 4/30/2023 2:38 AM
あ、 body を提供してる 外側の View が変わるんですか
2:38 AM
ちょっと試してみますね
Avatar
omochimetaru 4/30/2023 2:51 AM
@kebo struct MidViewA: View { let condition: Bool @State var count = 0 var body: some View { if condition { Counter(name: "T", count: $count) } else { Counter(name: "F", count: $count) } } } struct Counter: View { var name: String @Binding var count: Int var body: some View { Button { count += 1 } label: { Text("\(name)=\(count)") } } } struct ContentViewF: View { @State var condition = false var body: some View { VStack { MidViewA(condition: condition) Button { condition.toggle() } label: { Text("toggle") } } } } これでおっしゃっている状況を構成できてると思ったんですがどうでしょうか? 「MidViewA に関して、 condition が変化した場合、 MidViewA.count がリセットする」 (MidViewA が、 ContentViewA を再現しようとしたものです) という話だと思います。 しかし、動かしてみるとtoggleボタンを押してもカウントがリセットしませんでした。
Avatar
意図はその通りです.ちょっと見てみます.
🙏 1
Avatar
確かに iOS 16.5 ではおっしゃる通りでした.ひょっとしたら別の問題と混同して誤った情報を流してしまっていた可能性もありますが,どこかの OS バージョンから @ViewBuilder の扱いが変わった可能性もあるので,ちょっとシミュレータを落として古い OS で試してみます.
🆗 1
Avatar
以下のように ContentViewF 側で MidViewAonDisappear を生やした場合,iOS 16.5, iOS 15.0 時点では condition が変わっても呼ばれることはありませんが,iOS 14.0.1 では呼ばれてました.なので MidViewA が死ぬというのは iOS 14.x 時代の記憶だったようです. struct ContentViewF: View { ... var body: some View { VStack { MidViewA(condition: condition).onDisappear { print("onDisappear") } Button { condition.toggle() } label: { Text("toggle") } } } } ただ,MidViewA@State がリセットされることはなかったので,onDisappear が呼ばれる条件と @State がリセットされる条件はそもそも違っていたみたいですね.
Avatar
omochimetaru 4/30/2023 3:55 AM
おおなるほど・・・ iOS15では、 onAppear/DisappearとStateのリセットは同じタイミングなんですかね
Avatar
ストレージがカツカツで iOS 15.0 Simulator を消しちゃったので 15 ではすぐには試せないですが,iOS 16.5 と iOS 14.0.1 では以下のようなコード (omochimetaru さんが最初らへんに書かれていた必ずリセットされるコードと同等) で T から F に変わって値がリセットされる時に "onAppear F" と "onDisappear T" がこの順番でログに吐かれますね. struct Counter: View { let name: String @State var count = 0 var body: some View { Button("\(name)=\(count)") { count += 1 } .onAppear { print("onAppear \(name)") } .onDisappear { print("onDisappear \(name)") } } } struct ContentView: View { @State var condition = false var body: some View { VStack { if condition { Counter(name: "T") } else { Counter(name: "F") } Button("toggle") { condition.toggle() } } } } (edited)
Avatar
omochimetaru 4/30/2023 4:09 AM
なるほど。
Avatar
Avatar
omochimetaru
// 素早くトグルすれば値が生きている struct ContentViewE2: View { @State var toggle = false var body: some View { VStack { if toggle { VStack { TextView() Button { toggle.toggle() } label: { Text("toggle if") } } } else { VStack { TextView() Button { toggle.toggle() } label: { Text("toggle if") } } } } } }
このコードだと,iOS 16.5 では素早くトグルすると値が生き残って,その時たとえば T から F へ切り替えて素早く T へ戻す動作だったとすると,onAppear F と onDisappear F だけが吐かれて,onDisappear T は無い状況でした.ただ iOS 14.0.1 だと onDisappear T も出る代わりに値は必ずリセットされちゃってました.
4:14 AM
onAppearonDisappearTextView に生やした前提
Avatar
omochimetaru 4/30/2023 4:14 AM
Tのappear/disappearが起きない事と、stateが維持される事は一貫してるわけですね。
Avatar
ですね.
Avatar
Avatar
kebo
以下のように ContentViewF 側で MidViewAonDisappear を生やした場合,iOS 16.5, iOS 15.0 時点では condition が変わっても呼ばれることはありませんが,iOS 14.0.1 では呼ばれてました.なので MidViewA が死ぬというのは iOS 14.x 時代の記憶だったようです. struct ContentViewF: View { ... var body: some View { VStack { MidViewA(condition: condition).onDisappear { print("onDisappear") } Button { condition.toggle() } label: { Text("toggle") } } } } ただ,MidViewA@State がリセットされることはなかったので,onDisappear が呼ばれる条件と @State がリセットされる条件はそもそも違っていたみたいですね.
まあ↑みたいな例もあるので,onDisappear/onAppear が起きることと State が維持されることは一致することが多いというだけで必ず一致するとは言い切れないかもしれないですが...
Avatar
Avatar
kebo
まあ↑みたいな例もあるので,onDisappear/onAppear が起きることと State が維持されることは一致することが多いというだけで必ず一致するとは言い切れないかもしれないですが...
omochimetaru 4/30/2023 4:24 AM
iOS14のSwiftUIはいろいろ怪しいのでぶっ壊れたって事でスルーして良いと思います
👍 1
Avatar
こういうコードだと,あらかじめカウントアップした状態で,最初の toggle 連打時には deinit 0 が 1 回,それ以降の連打時には deinit 0 が 2 回ずつ出力されるので,@State の挙動としては condition が変わって Counter が変わると @State のための変数が新しく作られるけれど,前に使っていた変数がまだ破棄されず生きていたらそれを使い回すが新しく作った方も画面表示に使わないだけで裏では保持し続ける,みたいな挙動になっていそうですね. @State に参照型を持たせるか値型を持たせるかで挙動が変わりそうな気もするので Swift 5.9 ツールチェーンで @_moveOnly struct を作って使おうとしてみたんですが,State<T> みたいなジェネリクスでは noncopyable struct は使えないみたいで弾かれてしまいました... final class Count { let count: Int init(_ count: Int) { self.count = count } deinit { print("deinit \(count)") } } struct Counter: View { let name: String @State var count = Count(0) var body: some View { Button("\(name)=\(count.count)") { count = Count(count.count + 1) } } } struct ContentView: View { @State var condition = false var body: some View { if condition { VStack { Counter(name: "T") Button("toggle") { condition.toggle() } } } else { VStack { Counter(name: "F") Button("toggle") { condition.toggle() } } } } } (edited)
5:15 AM
ちなみに @StateObject@State だと挙動が違うので,上の例ではあくまでも @State の挙動を知るために @State に参照型を持たせてます.
Avatar
omochimetaru 4/30/2023 7:32 AM
連打生存バグは State + 値型でも起きるんですね。 (↑の例は State + 参照型ですが、手元で struct Count にして確認しました ) (edited)
Avatar
元々 Int で起こっているのでそれはそうだと思います.(Int の生存期間が知りたかったので class でラップした感じです) (edited)
Avatar
Avatar
kebo
こういうコードだと,あらかじめカウントアップした状態で,最初の toggle 連打時には deinit 0 が 1 回,それ以降の連打時には deinit 0 が 2 回ずつ出力されるので,@State の挙動としては condition が変わって Counter が変わると @State のための変数が新しく作られるけれど,前に使っていた変数がまだ破棄されず生きていたらそれを使い回すが新しく作った方も画面表示に使わないだけで裏では保持し続ける,みたいな挙動になっていそうですね. @State に参照型を持たせるか値型を持たせるかで挙動が変わりそうな気もするので Swift 5.9 ツールチェーンで @_moveOnly struct を作って使おうとしてみたんですが,State<T> みたいなジェネリクスでは noncopyable struct は使えないみたいで弾かれてしまいました... final class Count { let count: Int init(_ count: Int) { self.count = count } deinit { print("deinit \(count)") } } struct Counter: View { let name: String @State var count = Count(0) var body: some View { Button("\(name)=\(count.count)") { count = Count(count.count + 1) } } } struct ContentView: View { @State var condition = false var body: some View { if condition { VStack { Counter(name: "T") Button("toggle") { condition.toggle() } } } else { VStack { Counter(name: "F") Button("toggle") { condition.toggle() } } } } } (edited)
omochimetaru 4/30/2023 7:34 AM
State<T> みたいなジェネリクスでは noncopyable struct は使えないみたい
これはそうです。 Swiftのジェネリック抽象型 ( T の事 )は、Copyable が暗黙に含まれているからです。 func foo<T>(a: T) { let b = a // ←これが常に許されている print(b) }
t_naruhodo 1
Avatar
Avatar
kebo
元々 Int で起こっているのでそれはそうだと思います.(Int の生存期間が知りたかったので class でラップした感じです) (edited)
omochimetaru 4/30/2023 7:35 AM
元々は StateObject → class → propertyのInt という要素による構成だったので、一応まだこの辺の部品を疑ってました (edited)
Avatar
Avatar
omochimetaru
State<T> みたいなジェネリクスでは noncopyable struct は使えないみたい
これはそうです。 Swiftのジェネリック抽象型 ( T の事 )は、Copyable が暗黙に含まれているからです。 func foo<T>(a: T) { let b = a // ←これが常に許されている print(b) }
omochimetaru 4/30/2023 7:36 AM
ちなみにこれについては 暗黙の Copyable を除去する文法 <T: ~Copyable> が議論されてます。実装状況は追ってないです。
👀 1
Avatar
Avatar
omochimetaru
// 素早くトグルすれば値が生きている struct ContentViewE: View { @State var toggle = false var body: some View { if toggle { VStack { TextView() Button { toggle.toggle() } label: { Text("toggle if") } } } else { VStack { TextView() Button { toggle.toggle() } label: { Text("toggle if") } } } } } final class ViewModel: ObservableObject { @Published var count = 0 } struct TextView: View { @StateObject var viewModel: ViewModel init() { _viewModel = .init(wrappedValue: ViewModel()) } var body: some View { Button { viewModel.count += 1 } label: { Text("count: \(viewModel.count)") } } }
手元で確認してみたのですが、ContentViewEだとそもそもTextViewのonDisappearが素早く動かした時に呼ばれていないので、非表示なViewとしてSwiftUIに認識されていないかもですね... (ContentViewDのTextViewのonDisappearは呼ばれていますが...) (edited)
Avatar
Avatar
matsuji
手元で確認してみたのですが、ContentViewEだとそもそもTextViewのonDisappearが素早く動かした時に呼ばれていないので、非表示なViewとしてSwiftUIに認識されていないかもですね... (ContentViewDのTextViewのonDisappearは呼ばれていますが...) (edited)
omochimetaru 4/30/2023 7:52 AM
トグルするボタン自体が消え去るifセクションに包まれているわけですが、 ボタンが消えるアニメーションの時間の間だけ、ボタンを含むifセクション全体が生存を延長されている ような感じがします。 まあ見た目にはボタンはパッと切り替わってるんですけど・・・
👍 1
Avatar
Avatar
omochimetaru
トグルするボタン自体が消え去るifセクションに包まれているわけですが、 ボタンが消えるアニメーションの時間の間だけ、ボタンを含むifセクション全体が生存を延長されている ような感じがします。 まあ見た目にはボタンはパッと切り替わってるんですけど・・・
そうですね、Buttonが内部で持っている何かが悪さをしていそうな気がしました。 ContentViewEのButtonをText + onTapGestureに書き換えると、値が残ることはなくなりました Text("toggle if") .onTapGesture { toggle.toggle() }
8:03 AM
あー、なるほど、Buttonのactionでanimationが起きてるんですね (edited)
8:05 AM
これでも値は消えましたね Button { withAnimation(nil) { toggle.toggle() } } label: { Text("toggle if") }
Avatar
Avatar
matsuji
これでも値は消えましたね Button { withAnimation(nil) { toggle.toggle() } } label: { Text("toggle if") }
おお!アニメーションが悪さを起こしてるので間違いなさそう
Avatar
iOS17 以下で #Preview 怒られるので available つけてみたらプロジェクトのビルドは問題ないけど Xcode Preview は Syntax error になるんですね 😺 @available(iOS 17, *) #Preview { ContentView() }
Avatar
Kishikawa Katsumi 6/6/2023 7:13 PM
#Preview が生成するコードが17+なんですよね。自分でマクロ作って中で分岐するコードを生成するとよさそう。
Avatar
omochimetaru 6/6/2023 11:42 PM
展開後に新しい機能含まれてるのか。下位互換な実装にできそうなもんだが・・・ プレビューシステム自体の改善も含まれてるのかな
Avatar
マクロの中にマクロって書けるんですかね?
Avatar
Kishikawa Katsumi 6/7/2023 12:00 AM
下位互換にできると思うんですけどね。
Avatar
Avatar
Iceman
マクロの中にマクロって書けるんですかね?
omochimetaru 6/7/2023 12:01 AM
できますよ。Observableがそういう実装になってる。
Avatar
Kishikawa Katsumi 6/7/2023 12:01 AM
展開した先にもマクロ書けるの?
Avatar
omochimetaru 6/7/2023 12:01 AM
はい
Avatar
Kishikawa Katsumi 6/7/2023 12:01 AM
そうなのか。複雑だな。
Avatar
へぇ〜再帰的に展開されるんですね (edited)
Avatar
omochimetaru 6/7/2023 12:03 AM
The Swift Programming Language. Contribute to apple/swift development by creating an account on GitHub.
12:04 AM
↑ちょっとわかりにくいけど、 @Observable struct Foo { var foo: ... } の展開処理の結果として、 @ObservableTracked var foo: ... がプロパティに付与される部分。
12:06 AM
で、こういう事があるので、 memberAttributeマクロとaccessorマクロではどっちが先に展開されるか、みたいな規則もあったような気がする・・・
12:08 AM
https://github.com/apple/swift-evolution/blob/main/proposals/0389-attached-macros.md#ordering-of-macro-expansions いや、違うかも、展開順の影響を受けないように種別が設計されている・・・? (edited)
This maintains proposals for changes and user-visible enhancements to the Swift Programming Language. - swift-evolution/0389-attached-macros.md at main · apple/swift-evolution
12:10 AM
いや、それもちょっと違うな・・・ ここで書いてあるのは、2つの仕事をする一つのマクロがあるとき、それぞれの仕事ごとに元ソースが渡されて、結果がマージされるって言ってるだけだ
Avatar
Kishikawa Katsumi 6/7/2023 12:12 AM
マクロがマクロを生成するとどうなる?っていうのは議論にはあったと思うんだけど詳しくは覚えてないな。でもアリなんすね。
Avatar
omochimetaru 6/7/2023 12:13 AM
はい、少なくともObservableはそうなってる。
Avatar
Kishikawa Katsumi 6/7/2023 12:14 AM
ループしたりしないような仕組みがなんかあるんだろうな。
Avatar
omochimetaru 6/7/2023 12:14 AM
多分展開ステージ数を数えてて上限があるだけとかな気がする
12:18 AM
12:19 AM
マクロ Foo の展開結果に マクロ Foo が含まれてたらもう怒られるっぽい?
Avatar
Kishikawa Katsumi 6/7/2023 12:26 AM
明らかにループするしそれは合理的だな。
12:26 AM
一つ別のマクロを挟んだらどうなるんだろう。
12:26 AM
Aマクロを出力するBマクロをAマクロが出力。
Avatar
omochimetaru 6/7/2023 12:26 AM
ここだけだとわかんないですね、全部トラッキングしてるのか、一手しか見てないのか
12:26 AM
もうちょっとソース見てみないと。
12:28 AM
#Fib(2)#Fib(1) + #Fib(0)1 + 1 みたいな パターンは作れそうだけど作れないってことかなあ
Avatar
Kishikawa Katsumi 6/7/2023 1:13 AM
そういえばマクロがLinuxで使えるようにする対応が後回しなのはやっぱりSwiftUIでいろいろ使いたかったということなのかな。
Avatar
omochimetaru 6/7/2023 1:13 AM
Linux後回しとかあったんですね。
Avatar
Kishikawa Katsumi 6/7/2023 1:14 AM
今でもLinuxでは使えないと思うよ。
Avatar
omochimetaru 6/7/2023 1:14 AM
普通に同時に使えるようになるのと思い込んでた
Avatar
Avatar
Kishikawa Katsumi
今でもLinuxでは使えないと思うよ。
omochimetaru 6/7/2023 1:14 AM
ガッカリ・・・
Avatar
Kishikawa Katsumi 6/7/2023 1:14 AM
単純になんか難しいところがあるのかもしれないけど。
Avatar
へー
1:15 AM
なんでだろ
Avatar
omochimetaru 6/7/2023 1:15 AM
マクロプラグインとかのあたりはビルドシステム的にちょっとややこしいことしてるから、そういうのかなあ
Avatar
えっLinuxだと使えないんですね
Avatar
omochimetaru 6/7/2023 1:15 AM
普通にdylibをcompilerからロードしてるだけな気もするが・・・
Avatar
なるほどプラグイン周りか
Avatar
Kishikawa Katsumi 6/7/2023 2:16 AM
改めてやってみた。 Macros.swift:9:14: error: macros are not supported in this compiler public macro assert( 同じエラーだった。
Avatar
omochimetaru 6/7/2023 2:16 AM
あっけないエラーだ・・・
Avatar
Kishikawa Katsumi 6/7/2023 3:07 AM
Server-sideのRouting?とかにもマクロは有効みたいな気はするんだけどな。
Avatar
omochimetaru 6/7/2023 3:08 AM
まさにそういう事を考えてました
Avatar
Kishikawa Katsumi 6/7/2023 3:16 AM
期間中に聞ける機会があったら聞いてみよう
Avatar
Kishikawa Katsumi 6/7/2023 4:26 PM
ラボで聞いたらマクロがLinuxで使えないのは特に技術的な問題はなくて既存のテストの修正やCI環境の更新に手が回ってないということらしい。Soonって言ってた。
Avatar
なるほど
Avatar
Kishikawa Katsumi 6/7/2023 4:27 PM
Dougさんが申し訳なさそうに話してくれました。
Avatar
omochimetaru 6/7/2023 6:02 PM
おお、そうなのか、よかった
Avatar
Kishikawa Katsumi 6/7/2023 6:44 PM
No technical limitationって言ってました。
Avatar
普通にdylibをcompilerからロードしてるだけな気もするが・・・
Linux 出来てない理由とは関係ないけど普通のプラグインは実行形式+IPCです。SwiftPMのビルドプラグインと同じ形式。
2:33 PM
toolchainに同梱されてるプラグインはdylibです。
Avatar
Kishikawa Katsumi 6/9/2023 3:13 PM
IPC、なるほど。たしかに@main必要だった。
3:15 PM
私のWebのPlaygroundではLinuxでそのまま動かないので回避策としてSwift Syntaxでソースコードのマクロを置換して動かしてる。
Avatar
omochimetaru 6/9/2023 5:50 PM
おお、そうなのか。安全でいいな
Avatar
Kishikawa Katsumi 10/25/2023 7:28 AM
https://twitter.com/ojun_9/status/1715208317994483880 これおもしろいな。たぶんPreviewが自動的に前後にコードを挿入するからSwitch式の条件を満たさなくなるってことかな。で @ViewBuilderをつけるとResult BuilderになるからSwitch式じゃなくてResult BuilderのSwitchの一部になるんで行けるようになる、のかな。
t_naruhodo 3
7:28 AM
7:29 AM
レアケースなんだろうけどなかなか難しいエラーだ。たぶんSwitch式やIf式の制限はいずれ緩くなっていくと思うからそれで解決されるのかな。
Avatar
Discordでtwitter.comのプレビューが表示されない対策として、ドメイン部分をfxtwitter.comへ書き換えるというのがあるそうです。 https://fxtwitter.com/ojun_9/status/1715208317994483880
Swift 5.9からのSwitch式を用いてViewを返すと、普通のビルドは成功するがプレビュー時のビルドは失敗する @\ViewBuilderを付ければ、どちらの場合でもビルドは成功する
Avatar
Kishikawa Katsumi 10/25/2023 11:12 AM
へえー、便利だ。
Avatar
TwitterのプレビューのDiscord Botがこの前リリースされたので、これもよさそうです。 https://twitter.com/naayu1012/status/1711720742281486681 プレビューが表示されないのは、Discordのバグらしいので治るのがベストなんですが https://discordstatus.com/incidents/y823j8mbj7p6
Avatar
Kishikawa Katsumi 10/25/2023 2:35 PM
Twitterに関していろんな人がめちゃめちゃがんばってるんやな😊
👏 1
Avatar
Miwa / Ensan 11/9/2023 4:28 PM
https://developer.apple.com/documentation/swiftui/viewmodifier/content ViewModifierのtypealiasになってるContentってどういう型かわかりますか?
4:32 PM
実際の型は_ViewModifier_Contentというやつっぽいのですが、どうやってこの型を隠匿しているのかが謎です
Avatar
omochimetaru 11/9/2023 4:35 PM
なんだこりゃ。
4:35 PM
protocolの中のtypealiasってそもそもどうなるんでしたっけ。
4:37 PM
とりあえず、SwiftUI.frameworkの中にある、SwiftUI.swiftinterfaceファイルを見れば、
👀 1
4:37 PM
どう書いたらいいかはわかるかもしれません
Avatar
Miwa / Ensan 11/9/2023 4:48 PM
与える型を明示的に指定すればtypealiasも動きますが、publicなprotocolの場合はpublicな型しか指定できません protocol P { associatedtype Foo typealias Bar=Text }
👀 1
Avatar
Miwa / Ensan 11/9/2023 5:00 PM
定義上変な感じは特にないので、アンダースコア付きのidentifierが自動的に隠されたりしてるのかな public protocol ViewModifier { static func _makeView(modifier: SwiftUI._GraphValue<Self>, inputs: SwiftUI._ViewInputs, body: @escaping (SwiftUI._Graph, SwiftUI._ViewInputs) -> SwiftUI._ViewOutputs) -> SwiftUI._ViewOutputs static func _makeViewList(modifier: SwiftUI._GraphValue<Self>, inputs: SwiftUI._ViewListInputs, body: @escaping (SwiftUI._Graph, SwiftUI._ViewListInputs) -> SwiftUI._ViewListOutputs) -> SwiftUI._ViewListOutputs @available(iOS 14.0, macOS 11.0, tvOS 14.0, watchOS 7.0, *) static func _viewListCount(inputs: SwiftUI._ViewListCountInputs, body: (SwiftUI._ViewListCountInputs) -> Swift.Int?) -> Swift.Int? associatedtype Body : SwiftUI.View @SwiftUI.ViewBuilder @_Concurrency.MainActor(unsafe) func body(content: Self.Content) -> Self.Body typealias Content = SwiftUI._ViewModifier_Content<Self> } @available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *) public struct _ViewModifier_Content<Modifier> : SwiftUI.View where Modifier : SwiftUI.ViewModifier { public static func _makeView(view: SwiftUI._GraphValue<SwiftUI._ViewModifier_Content<Modifier>>, inputs: SwiftUI._ViewInputs) -> SwiftUI._ViewOutputs public static func _makeViewList(view: SwiftUI._GraphValue<SwiftUI._ViewModifier_Content<Modifier>>, inputs: SwiftUI._ViewListInputs) -> SwiftUI._ViewListOutputs @available(iOS 14.0, macOS 11.0, tvOS 14.0, watchOS 7.0, *) public static func _viewListCount(inputs: SwiftUI._ViewListCountInputs, body: (SwiftUI._ViewListCountInputs) -> Swift.Int?) -> Swift.Int? @available(iOS 14.0, macOS 11.0, tvOS 14.0, watchOS 7.0, *) @_alwaysEmitIntoClient public static func _viewListCount(inputs: SwiftUI._ViewListCountInputs) -> Swift.Int? { _viewListCount(inputs: inputs) { _ in nil } } public typealias Body = Swift.Never }
Avatar
omochimetaru 11/9/2023 5:02 PM
普通ですね・・・
5:03 PM
アンスコの特別処理があるのか、コンパイラの中に特別対応があるのか
5:03 PM
謎だ。
Avatar
Miwa / Ensan 11/9/2023 5:03 PM
あれ、でも_ViewModifier_ContentはSwiftUI.Viewにconformしてるのに、bodyの定義がないですね (edited)
Avatar
omochimetaru 11/9/2023 5:04 PM
Body = Neverの場合はextensionがあるとか・・・?
Avatar
Miwa / Ensan 11/9/2023 5:05 PM
そう思ったのですが、body自体がないと怒られるんですよね struct MyModifier: ViewModifier { func body(content: Content) -> some View { // Value of type 'MyModifier.Content' (aka '_ViewModifier_Content<MyModifier>') has no member 'body' let s = content.body return EmptyView() } }
5:06 PM
しかし、これはエラーです private struct MyView: View { typealias Body = Never }
Avatar
omochimetaru 11/9/2023 5:08 PM
なるほど。謎だ。
Avatar
Miwa / Ensan 11/9/2023 5:12 PM
無理やり呼び出せば呼べるので、謎 struct TwerkModifier: ViewModifier { func body(content: Content) -> some View { Wrapper(view: content) } struct Wrapper<T: View>: View { var view: T var body: some View { return view.body } } } struct UsageView: View { var body: some View { // SwiftUI/DynamicProperty.swift:338: Fatal error: ViewWrapper<_ViewModifier_Content<TwerkModifier>> may not have Body == Never Text("Foo") .modifier(TwerkModifier()) } } (edited)
Avatar
Xcodeから見るときはアンスコIdentifierの色々なものが隠されてると思ってました
11:40 PM
swiftinterfaceにはちゃんと書いてあるので、Xcode独自の仕様かなと
Avatar
SwiftUIじゃなくても隠れるんだろうか? (edited)
Avatar
UIKitとかConcurrencyでも隠れてませんっけ?
11:42 PM
@_disfavorerdOverloadとかも見えてないような
11:42 PM
@_inheritActorContextとかも
Avatar
inheritActorContextって見えないんでしたっけ? じゃあ俺がそれ知ってるのはコンパイラの方をみたからか
Avatar
おお、SkipUnderscoredKeywords‎とやらがある。
Avatar
コンパイラのほうにあったんですね
Avatar
じゃあ
Avatar
ちなみにNeverはViewです
Avatar
サードパーティのコードでも
11:49 PM
publicだけど隠したい時はアンスコにするのは機能的な意味もあるのか
Avatar
xcframeworkとして参照するときしか効果ないケース多いんじゃないでしょうか
11:49 PM
Package.swiftで参照するとそのままソース見に行く形になるので。
Avatar
たしかに。
11:50 PM
そこからコマンドコントロール上でヘッダーに飛べるけど
11:50 PM
そこでは消えてるのかも。
Avatar
Avatar
Miwa / Ensan
あれ、でも_ViewModifier_ContentはSwiftUI.Viewにconformしてるのに、bodyの定義がないですね (edited)
本題じゃないんですが Body = NeverはSwiftUIの組み込みのViewだとよく見られて (<Label>とか<Content>を持ってないViewはかなりがBody = Neverです) 例えばVStackとかがわかりやすくて VStackの正体は_VariadicViewですが BodyはNeverになっていて別で_treeを持って中身を管理してそうです @available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *) @frozen public struct VStack<Content> : SwiftUI.View where Content : SwiftUI.View { @usableFromInline internal var _tree: SwiftUI._VariadicView.Tree<SwiftUI._VStackLayout, Content> @inlinable public init(alignment: SwiftUI.HorizontalAlignment = .center, spacing: CoreFoundation.CGFloat? = nil, @SwiftUI.ViewBuilder content: () -> Content) { _tree = .init( root: _VStackLayout(alignment: alignment, spacing: spacing), content: content()) } public static func _makeView(view: SwiftUI._GraphValue<SwiftUI.VStack<Content>>, inputs: SwiftUI._ViewInputs) -> SwiftUI._ViewOutputs public typealias Body = Swift.Never } こういう純粋にvar body: some Viewで表現できない事情がある時にBody = Neverになってそうです Icemanさんがいう通りSwift.NeverはViewでbodyではなんかを返してそう @available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *) extension Swift.Never { public typealias Body = Swift.Never public var body: Swift.Never { get } } (edited)
Avatar
var body の実装がない型は他にもあるけどなんでだろう (edited)
Avatar
なんででしょうねこれ...かなりあるんですよね ImageとかTextとかもなくて、むしろ組み込みでbodyの実装がある方が珍しいです (<Label>とかの型パラを持つViewの場合はbodyがあるケースが多い) (edited)
Avatar
どっかで言及されてた気がするんですけど、SwiftUIのViewにはレイアウトに関するもので実体を持たないものと、実体を持つものの2種類が存在していて、前者が軒並みNeverになってるんでしょうね
1:01 AM
実体を持つとは、Surfaceにレンダリングするプロセスを持つかどうか的な意味です
Avatar
逆に実態を持つから、body: some Viewだけでは実現不可能でゴチャゴチャやってるのかなと思ったりしています (edited)
Avatar
Body = Neverだったとしても、 var body: Never { ... } は書かないとダメですよね?
Avatar
そうですね
Avatar
1:03 AM
本当に、ない
🤔 1
1:03 AM
あ、TextはViewじゃないのか
1:04 AM
いや、Viewだな
Avatar
いえ、Viewだと思います
1:05 AM
そもそも僕は理解できてないんですが @available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *) extension Swift.Never { public typealias Body = Swift.Never public var body: Swift.Never { get } } って定義がswiftinterfaceにあるのに private struct MyView: View { typealias Body = Never } がエラーになるのは言語的にどうなってるんでしょう...? 特定のモジュール内でのみ有効とかできるんですか? (edited)
Avatar
逆か、実体を持つからこそ自身が葉だからNeverになれるのかな (edited)
Avatar
それは、そうじゃないすか?
Avatar
Avatar
kntk
そもそも僕は理解できてないんですが @available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *) extension Swift.Never { public typealias Body = Swift.Never public var body: Swift.Never { get } } って定義がswiftinterfaceにあるのに private struct MyView: View { typealias Body = Never } がエラーになるのは言語的にどうなってるんでしょう...? 特定のモジュール内でのみ有効とかできるんですか? (edited)
Never型に対してbodyプロパティを生やす話は、 MyViewのbodyプロパティと関係ないでしょう
1:06 AM
MyViewのbodyプロパティがNeverを返してても、MyViewのbodyプロパティの実装は必要です
🙇‍♂️ 1
1:06 AM
extensionで実装が与えられてるのはNeverのbodyプロパティでMyViewじゃないから (edited)
Avatar
ああ、勘違いしてました NeverがViewじゃないって怒られるわけじゃないんですね、すみません 普通にbodyがないって怒られるんですね (edited)
Avatar
そうですね
Avatar
1:07 AM
Textのvar bodyはfatal errorが実装されているようだ
😿 1
1:07 AM
もしかして、 unavailable指定されていると、swiftinterfaceには出てこないのか?
Avatar
bodyがないならどこからTextの実態を取得してるんですかねぇ... 見えてないtreeみたいなのがあったりして
Avatar
それは、Textから先には子供はないから、要らないんじゃないすか?
1:19 AM
Text.bodyが取れなくてもText自体はすでに取れているのでTextの内部情報を取得して、レンダリングするだけ。
1:20 AM
逆か、実体を持つからこそ自身が葉だからNeverになれるのかな
↑この通りで、「葉」だから。
(edited)
Avatar
なるほど、葉の場合はいらないですね ただ葉じゃないはずのVStackもbodyがなさそうです
Avatar
VStackもVStackであるということがわかれば固定レイアウトなので十分だと思います。
1:23 AM
仮にbodyがあったとしても、VStackのbodyはVStackでしか実装できないので、堂々巡りになりません?
Avatar
なるほど、それで子の情報はさっきのtreeを見ればいいわけですね internal var _tree: SwiftUI._VariadicView.Tree<SwiftUI._VStackLayout, Content> (edited)
👀 1
Avatar
てか、これは、 @usableFromInline だから internal だけどswiftinterfaceに表示されているけど
1:25 AM
通常のinternalだったら、取得インターフェースは外には見えないけど、SwiftUIモジュール内部からは見えるので
1:25 AM
swiftinterfaceからは議論できない部分だと思います。
👍 1
Avatar
これスレッド読み返したんですが、bodyが直接呼び出せない理由は未解決ですか……?
Avatar
表示されないと、型システム的にも無い事になっていると思いました。
5:21 AM
コンフォーマンスとは矛盾するけど、実際プロトコル通すと呼べたし。
Avatar
プロトコルにpublicなconformanceを持つ場合、その要件はすべてpublicにならないといけないと思うのですが、これに抜け穴があるってことなんでしょうか
Avatar
そうだと思います。
5:22 AM
実は隠せる。間接的に呼べるけれども。
Avatar
なるほど
Avatar
アンスコ開始のprotocolで隠れたconformanceがあって、そちらが本体のprotocolを満たしてる、みたいなのは割とApple製のframeworkで見られるパターンだと思います
5:25 AM
SwiftUIの場合は、_makeViewや_treeがそれになってるんじゃないかと思います
👀 3
Avatar
I’m thrilled to announce the tech preview of Skip: dual-platform app development in Swift. You write a modern iOS app in Swift and SwiftUI. Skip’s Xcode plugin generates a native Android Kotlin and Compose version! Docs: https://skip.tools/docs Video: https://skip.tools/tour
Avatar
XcodeでUnitテスト実行するとAndroid向けのUnitテストもXcodeからGradle経由で実行し始めるの面白かった
Avatar
SwiftSyntaxを活用してKotlinにトランスパイルするっていうのは新しいですね J2ObjCやKotlin MultiplatformなどAndroid→iOSはあったけどiOS→Androidは初めて見たかも
Avatar
こんばんは、swiftuiを久しぶりに触った者です @ObservableはObservableObjectの完全な置き換えで、bindingが必要なところは@Bindableに置き換えるという認識であってますか?
Avatar
Avatar
xsuiyoku
こんばんは、swiftuiを久しぶりに触った者です @ObservableはObservableObjectの完全な置き換えで、bindingが必要なところは@Bindableに置き換えるという認識であってますか?
僕も最初勘違いしていたのですが、 @Bindable@Binding の置き換えではありません。 @Bindable はProperty WrapperのProjected Value( $ )を使えるようにするためのものです。
2:33 PM
たとえば、リファレンス https://developer.apple.com/documentation/swiftui/bindable の例では、 @Observable class Book: Identifiable { var title = "Sample Book Title" var isAvailable = true } struct BookEditView: View { @Bindable var book: Book @Environment(\.dismiss) private var dismiss var body: some View { Form { TextField("Title", text: $book.title) Toggle("Book is available", isOn: $book.isAvailable) Button("Close") { dismiss() } } } } BookEditView 自体においては Book@Bindable である必要はありません。もし book のプロパティが変更されれば BookEditView は再レンダリング( body が再実行)されますし、 BookEditView の中で book のプロパティを変更すれば、 BookEditViewbook を渡した元の View 等にも反映されます。
2:36 PM
しかし、 TextField("Title", text: $book.title) のように、 book のプロパティをさらに先の View に渡そうとするときに困ります。もし let book: Book だと( Book はクラスなので、Property Wrapper( @Bindable 等)を使わないのであれば var である必要がありません。単に let book: Book としても、 @observable なクラスのプロパティの更新は追跡され、再レンダリングされます)、 $ を使うことができません。 (edited)
2:36 PM
その場合は、 TextField("Title", text: Binding( get: { book.ttile }, set: { book.title = $0 } )) と書くことになってしまいます。
2:37 PM
これが面倒なので、 $ が使えるように @Bindable を利用します。
Avatar
Avatar
koher
これが面倒なので、 $ が使えるように @Bindable を利用します。
丁寧にありがとうございます!コード付きで大変分かりやすく助かります。 ObservableObjectが@Observableに置き換えになって、projected Valueを簡単に取得できなくなったため、@Bindableが登場したという感じですかね?(本当の経緯は分かりませんが) 以下のような違いという認識で合っていますか? @Binding:projected Valueを得る @Bindable:@Observableしたクラスのprojected Valueを簡単に得る
Avatar
Avatar
xsuiyoku
丁寧にありがとうございます!コード付きで大変分かりやすく助かります。 ObservableObjectが@Observableに置き換えになって、projected Valueを簡単に取得できなくなったため、@Bindableが登場したという感じですかね?(本当の経緯は分かりませんが) 以下のような違いという認識で合っていますか? @Binding:projected Valueを得る @Bindable:@Observableしたクラスのprojected Valueを簡単に得る
@Binding は渡した先と元で、値の変更を伝え合うためのものです。たとえば、 Book@ObservableObject の場合、単純に book.title を渡したのでは、変更を伝え合うことはできませんよね? book.title がその後変更されても渡した先には伝わりませんし、渡した先で title を変更しても book.title が変更されるわけではありません。
Avatar
Avatar
koher
@Binding は渡した先と元で、値の変更を伝え合うためのものです。たとえば、 Book@ObservableObject の場合、単純に book.title を渡したのでは、変更を伝え合うことはできませんよね? book.title がその後変更されても渡した先には伝わりませんし、渡した先で title を変更しても book.title が変更されるわけではありません。
参照を渡すような感じですか?
Avatar
Avatar
xsuiyoku
参照を渡すような感じですか?
はい、 Binding はそんな感じです。 Binding 自体は全く特殊な型ではなく、 @Binding なプロパティが変更されても body を再実行するような機能も持ちません。ただ getset を持つだけです。
3:08 PM
@Binding なプロパティが変更されたときにそのViewの body が再実行されるのは、 Binding を渡した側のViewで body が再実行され、それに伴って渡された側の View も更新されるからです。
3:13 PM
struct FooView: View { @State var user: User var body: some View { Checkbox(isOn: $user.isPublic) } } struct Checkbox: View { @Binding var isOn: Bool var body: some View { Button { isOn.toggle() } label: { Image(isOn ? "Checkbox-On" : "Checkbox-Off") } } } とした場合、 user.isPublic が更新されるとそれを @Binding で保持する Checkboxbody が再実行されるのではなく、 user@State で保持する FooViewbody が再実行されます。その結果として、 Checkbox(isOn: $user.isPublic) が新しい isPublic に対して実行され、 Checkbox が更新されます。
Avatar
Avatar
koher
struct FooView: View { @State var user: User var body: some View { Checkbox(isOn: $user.isPublic) } } struct Checkbox: View { @Binding var isOn: Bool var body: some View { Button { isOn.toggle() } label: { Image(isOn ? "Checkbox-On" : "Checkbox-Off") } } } とした場合、 user.isPublic が更新されるとそれを @Binding で保持する Checkboxbody が再実行されるのではなく、 user@State で保持する FooViewbody が再実行されます。その結果として、 Checkbox(isOn: $user.isPublic) が新しい isPublic に対して実行され、 Checkbox が更新されます。
渡した先のビュー(Checkbox)だけ更新されると思っていました、、、 大変よく分かりました。ありがとうございます! 最後に、これらの情報を得た学習リソースがあれば教えていただけると幸いです。勉強したいです🔥
👍 1
Avatar
Avatar
xsuiyoku
渡した先のビュー(Checkbox)だけ更新されると思っていました、、、 大変よく分かりました。ありがとうございます! 最後に、これらの情報を得た学習リソースがあれば教えていただけると幸いです。勉強したいです🔥
WWDCなどのセッションは役に立つと思います。また、実際に動かしてみて確認するのも良い方法だと思います。前述の挙動も print 文を仕込んでみたりすれば body が再実行されているかわかりますし、グローバル変数で保持された値のプロパティを無理やり Binding(get: { ... }, set: { ... }) で渡してみても、更新による再実行が起こらないことを確認できたりします。
t_arigatou 1
t_kami 1
Avatar
Avatar
koher
WWDCなどのセッションは役に立つと思います。また、実際に動かしてみて確認するのも良い方法だと思います。前述の挙動も print 文を仕込んでみたりすれば body が再実行されているかわかりますし、グローバル変数で保持された値のプロパティを無理やり Binding(get: { ... }, set: { ... }) で渡してみても、更新による再実行が起こらないことを確認できたりします。
無理やりBinding(…)で渡しても再描画されないことを確認しました!このように確かめる方法もあるのですね。参考になります。次は、WWDC23のDiscover Observation in SwiftUIあたりを見てみようと思います! 本当にありがとうございました!
👍 1
Avatar
@Binding については昔こんな記事も書きましたので、よろしければご参考に:https://qiita.com/lovee/items/caef6df4baf1a3085441 そしてObservationへの移行についてはこちらも: https://qiita.com/lovee/items/4909d0166bba973b3bc0
よくこのようなコード目にします:struct MainView: View { @State var inputText: String var body: some View { VStack …
TL;DR:Combine をやめて、Observation に移行しよう。今年の WWDC では、アップルがさりげなく新しいデータ追跡フレームワークを公開しました:Observation です。…
t_arigatou 3
Avatar
SomeView() .onLongPressGesture(minimumDuration: 2) { // ... } onPressingChanged: { isPressing in print(isPressing) // ←途中で指離してもちゃんと呼ばれて `isPressing` が `false` になる } LongPressGesture(minimumDuration: 2) .onChanged { isPressing in print(isPressing) // ←指離しても呼ばれない、押した瞬間だけ `isPressing` が `true` になって呼ばれる } .onEnded { // ... }
3:40 PM
↑これって仕様ですかバグですか 😇
Avatar
https://qiita.com/fuziki/items/6fe7a304b30146ba43c7 SwiftUI Viewの中にクロージャーを設けることは結構あるかと思いますが、みなさんも上の記事のように、更新を抑えるためにSwiftUI ViewにEquatableを継承してクロージャーを差分チェックから除外する実装をしているのでしょうか? 今回実装しているのはただのボタンなので、何度も更新されようが構いませんが、気になっています。
SwiftUIでEquatableなView(EquatableView)で冗長な更新を防ぐSwiftUIを使うと良い感じにUIを作ることができますしかし、特に大規模なアプリを作成していると、た…
Avatar
一般的にキャッシュはその実装自体が余分なのと、速くなるとは限らないのと、バグの原因になるという点で闇雲に実装するものではないと思います。
❤️ 1
2:46 AM
うまく効いたときの効果は絶大なので要所要所で使っていくのがいいと思います
Avatar
ありがとうございます!
Avatar
Avatar
takenoko
https://qiita.com/fuziki/items/6fe7a304b30146ba43c7 SwiftUI Viewの中にクロージャーを設けることは結構あるかと思いますが、みなさんも上の記事のように、更新を抑えるためにSwiftUI ViewにEquatableを継承してクロージャーを差分チェックから除外する実装をしているのでしょうか? 今回実装しているのはただのボタンなので、何度も更新されようが構いませんが、気になっています。
それとその記事は ObservableObject ベースでの議論で、その仕組みはSwiftUIのレイアウトエンジンは ObservableObject 全体を objectWillChange Publisherの購読によって監視してるので、何か一つでもプロパティーに更新があったら ObservableObject の全てのプロパティーに比較処理が走ってしまいます; ところが今iOS 17以上なら使える Observation フレームワークの @Observable なら、一つ一つのプロパティーが監視対象になるので、余計な比較処理や再レンダリング処理が省かれてだいぶパフォーマンスが良くなってるはずですので、iOS 17未満の対応が必要なければ是非こちらの利用を強くお勧めします 🙋
❤️ 1
Avatar
skip.tools 触ってみました。 Swiftで書いて実行するだけで iOS simulator と Android emulator が動くの面白いですね ただ、MapKit 使おうとしたらエラーになったようなので(深掘りはしてないです)、要件の 95% カバーしているは言い過ぎではないかなあと思いました。
Avatar
Avatar
dictav
skip.tools 触ってみました。 Swiftで書いて実行するだけで iOS simulator と Android emulator が動くの面白いですね ただ、MapKit 使おうとしたらエラーになったようなので(深掘りはしてないです)、要件の 95% カバーしているは言い過ぎではないかなあと思いました。
それ実は自分も試してみたんですが、protocol の定義で Self 使えなかったりみたいなKotlin起因の制限がやはりあるので、Kotlinを知らない状態でやるの結構怖いなと思いました…
Avatar
Avatar
dictav
skip.tools 触ってみました。 Swiftで書いて実行するだけで iOS simulator と Android emulator が動くの面白いですね ただ、MapKit 使おうとしたらエラーになったようなので(深掘りはしてないです)、要件の 95% カバーしているは言い過ぎではないかなあと思いました。
こんな感じで
👍 1
Avatar
Avatar
lovee
こんな感じで
そうですね。 「Swift だけで Android アプリが書ける!」ようなものではなくて、移植を助けてれるツールという感じでしょうか。 それでも、もう少しサポート範囲を広げてくれないと使いづらいと感じます。
t_sorena 1
Avatar
Skip つくった元同僚だったりする
🥰 1
t_tsuyoi 2
1:02 PM
ずっとiOSのUIやってた人
Avatar
@We こちらのチャネルの方が良いかなと思ったのでこちらに書きますね. 少なくとも Apple Developer Forums では投稿はありませんでした. そして iOS 17.4 だと TextField だけじゃなくて Picker とか Menu とか TextEditor でも同様みたいですね. ただ,ListNavigationStack の中にあれば問題ないみたいなので,SwiftUI の List って初期の頃から触ってると NavigationView (今の NavigationStack) の中で使われること前提で作られている部分がかなり多かったので,この問題に関しても ListNavigationStack の中で使ってねってことなのかもしれません. https://discord.com/channels/291054398077927425/291054454793306112/1235477558863069234
Exported 1,491 message(s)
Timezone: UTC+0